Benjamin Lim

'blym'

why?

  • Ultra responsive
  • JavaScript everywhere
  • Open source

introduction

  • Build a simple chat application
  • Step by step tutorial
  • Use git checkout to catch up :)
  • Ask questions!

setup

curl https://install.meteor.com/ | sh
https://www.meteor.com/install
https://c9.io/

install

cloud

Windows

OSX / Linux

Cloud 9

setup

git clone https://github.com/bumbleblym/meteor-cinder.git
https://github.com/bumbleblym/meteor-cinder.git
  1. Create New Workspace
  2. Clone from URL
  3. Select Meteor

install

cloud

launch

#step-0

cd meteor-cinder
meteor create app
meteor
cd workspace
meteor create app
meteor --port $IP:$PORT

install

cloud

view

#step-0

  1. Preview
  2. Preview Running Application
http://localhost:3000/

install

cloud

transform

#step-0

<head>
  <title>Meteor Cinder</title>
</head>

Try editing the title:

The page updates automatically!

This is called a 'hot code push'.

app.html

app.html 

spacebars

#step-0

<body>
  <h1>Welcome to Meteor!</h1>

  {{> hello}}
</body>

<template name="hello">
  <button>Click Me</button>
  <p>You've pressed the button {{counter}} times.</p>
</template>

{{> hello}}  and {{counter}} are Spacebars expressions that tell Meteor what to insert.

app.html 

spacebars

#step-0

{{> hello}}

'Include a template named "hello" here'

<template name="hello">
  <button>Click Me</button>
  <p>You've pressed the button {{counter}} times.</p>
</template>

Meteor parses code within <template> tags and creates named template objects.

<body>
  <h1>Hello, Meteor!</h1>

  {{> hello}}
  {{> hello}}
</body>

app.html 

app.html 

Try adding another {{> hello}} template inclusion tag:

spacebars

#step-0

{{> hello}}

Try moving the template section to a new file:

<template name="hello">
  <button>Click Me</button>
  <p>You've pressed the button {{counter}} times.</p>
</template>

hello.html 

To be organized:

  • Use the template name as the file name
  • 1 template per file

spacebars

#step-0

{{counter}}

'Look up the property or template helper named "counter"'

Template.hello.helpers({
  counter: function () {
    return Session.get('counter');
  }
});

app.js

templates

#step-0

helpers

Template.hello.helpers({
  counter: function () {
    return Session.get('counter');
  }
});

app.js

Template.hello.events({
  'click button': function () {
    // increment the counter when button is clicked
    Session.set('counter', Session.get('counter') + 1);
  }
});

app.js

events

Event map object that specifies how events should be handled

session

#step-0

  • Provides a global object that stores key-value pairs
  • Useful for storing temporary state
  • Methods you'll use:
Session.setDefault('counter', 0);

Session.get('counter');

Session.set('counter', Session.get('counter') + 1);

app.js

scope

#step-0

if (Meteor.isClient) {

  ...

if (Meteor.isServer) {

app.js

In short:

  • Create client, server, lib, public directories
  • No more if statements in client and server directories!

abort

#step-0

How do I stop the application?

How do I go to step #?

I want to save my changes:

CTRL + C

I don't want to save my changes:

git stash -u
git checkout -t origin/step-#
git add .
git commit -m "Description of my changes"
git checkout -t origin/step-#

packages

#step-1

How do I add Bootstrap?

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/...">
meteor add twbs:bootstrap

Atmosphere is a catalog where you can find more Meteor packages.

What other packages are there?

  • Remove generated files
  • Add client, server, lib directories
  • Add the Iron Router package

How do we display different pages?

iron router

#step-1

...

meteor-platform
autopublish
insecure
twbs:bootstrap
...

meteor-platform
autopublish
insecure
twbs:bootstrap
iron:router

.meteor/packages 

.meteor/packages 

Add the Iron Router package:

Specify the default layout template:

Router.configure({
  layoutTemplate: 'layout'
});

lib/router.js 

layout

#step-1

We'll use the Dashboard component:

<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>Cinder</title>
</head>

Add the head tag (not a template!):

Copy dashboard.css to client/templates/common/dashboard.css

client/templates/common/head/head.html 

layout

#step-1

<template name="layout">
  <nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container-fluid">
      <div class="navbar-header">
        ...

</template>

Create the layout template:

Don't forget the <template> tags!

client/templates/common/layout/layout.html 

refactor

#step-1

<template name="layout">
  {{> navbar}}

  <div class="container-fluid">
    <div class="row">
      <div class="col-sm-3 col-md-2 sidebar">
        ...

Extract the navbar template:

client/templates/common/layout/layout.html 

<template name="navbar">
  <nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container-fluid">
      <div class="navbar-header">
        ...

</template>

client/templates/common/navbar/navbar.html 

refactor

#step-1

<template name="layout">
  {{> navbar}}

  <div class="container-fluid">
    <div class="row">
      {{> sidebar}}
      ...

Do the same for the sidebar:

client/templates/common/layout/layout.html 

<template name="sidebar">
  <div class="col-sm-3 col-md-2 sidebar">
    <ul class="nav nav-sidebar">
      ...

</template>

client/templates/common/sidebar/sidebar.html 

routes

#step-2

<template name="layout">
  {{> navbar}}

  <div class="container-fluid">
    <div class="row">
      {{> sidebar}}
      <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
        {{> yield}}
      </div>
    </div>
  </div>
</template>

Add a {{> yield}} helper:

client/templates/common/layout/layout.html 

{{> yield}} is a placeholder for content to be inserted, depending on the route.

routes

#step-2

...

Router.route('/', {
  name: 'home'
});

Create a route:

lib/router.js 

'For the path "/", create a route named "home" and yield the template named "home".'

For more options, refer to the guide.

routes

#step-2

<template name="home">
  <div class="page-header">
    <h1>Cinder <small>Any gripe can change your life.</small></h1>
  </div>
</template>

Create the home template:

client/templates/views/home/home.html 

templates directory structure

  • common: navbars, sidebars, footers, etc.
  • views: route specific templates
  • shared: templates that are used by different views

accounts

#step-3

Time to add more packages!

meteor add accounts-password
meteor add useraccounts:bootstrap

User Accounts is a package that provides commonly used accounts related templates e.g. sign in button

Let's update our navbar:

  • Add a home link
  • Remove links and search form
  • Add sign in button

accounts

#step-3

  ...
  <a class="navbar-brand" href="{{pathFor route='home'}}">Cinder</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
  <ul class="nav navbar-nav navbar-right">
    <li>{{> atNavButton}}</li>
  </ul>
</div>
...

client/templates/common/navbar/navbar.html 

Add some padding:

#navbar > ul > li:last-child {
  padding-right: 15px;
}

client/templates/common/navbar/navbar.css 

Update navbar:

accounts

#step-3

AccountsTemplates.configureRoute('signIn');
AccountsTemplates.configureRoute('signUp');

lib/useraccounts.js 

Configure routes:

More routes for resetting/changing passwords can be found here

accounts

#step-3

Add username field:

...

var pwd = AccountsTemplates.removeField('password');
AccountsTemplates.removeField('email');
AccountsTemplates.addFields([{
    _id: "username",
    type: "text",
    displayName: "username",
    required: true,
    minLength: 5,
  },
  pwd
]);

lib/useraccounts.js 

accounts

#step-3

Add username to navbar:

...

<ul class="nav navbar-nav navbar-right">
  {{#if loggingIn}}
    <li><p class="navbar-text">Signing in...</p></li>
  {{else}}
    {{#if currentUser}}
      <li><p class="navbar-text">{{currentUser.username}}</p></li>
    {{/if}}
  {{/if}}
  <li>{{> atNavButton}}</li>
</ul>
...

client/templates/common/navbar/navbar.html 

{{#if}} and {{else}} are block tags. There's also {{#unless}}. {{#each}} and {{#with}} are used to set data context; we'll see them soon.

accounts

#step-3

Additional styling:

#navbar > ul > li:last-child {
  padding-left: 15px;
  padding-right: 15px;
}

client/templates/common/navbar/navbar.css 

channels

#step-4

meteor add anti:fake

Use Fake to generate text for prototyping:

Template.sidebar.helpers({
  channels: function() {
    var channels = [];

    _(10).times(function() {
      channels.push({
        name: Fake.word().toLowerCase()
      });
    });

    return channels;
  }
});

client/templates/common/sidebar/sidebar.js 

Create a helper to generate 'channels':

Underscore.js is also a great way to learn JavaScript.

channels

#step-4

Use our channels helper:

<template name="sidebar">
  <div class="col-sm-3 col-md-2 sidebar">
    <ul class="nav nav-sidebar">
      {{#each channels}}
        <li><a href="#"># {{name}}</a></li>
      {{/each}}
    </ul>
  </div>
</template>

client/templates/common/sidebar/sidebar.html 

...

channels.push({
  name: Fake.word().toLowerCase()
});

...

client/templates/common/sidebar/sidebar.js 

channels

#step-4

Add channel route:

...

Router.route('/channel/:name', {
  name: 'channel'
});

lib/router.js 

'../channel/cinder/messages/YC6haHD6g5pZE2JGA?q1=s1&q2=s2#hash'

Router.route('/channel/:name/messages/:messageId', function () {
  var channel = this.params.name;        // 'cinder'
  var messageId = this.params.messageId; // 'YC6haHD6g5pZE2JGA'

  var query = this.params.query;         // { q1: s1, q2: s2 }
  var hash = this.params.hash;           // 'hash'
});

channels

#step-4

Accessing route parameters:

Within the RouteController:

  • this.params

Outside:

  • Router.current()

channels

#step-4

Add channel links:

<template name="sidebar">
  <div class="col-sm-3 col-md-2 sidebar">
    <ul class="nav nav-sidebar">
      {{#each channels}}
        <li><a href="{{pathFor route='channel'}}"># {{name}}</a></li>
      {{/each}}
    </ul>
  </div>
</template>

client/templates/common/sidebar/sidebar.html 

channels

#step-4

Create channel template:

Add channelName helper:

Template.channel.helpers({
  channelName: function() {
    return this.params.name;
  }
});
<template name="channel">
  <div class="page-header">
    <h1>{{channelName}}</h1>
  </div>
</template>

client/templates/views/channel/channel.html 

client/templates/views/channel/channel.js 

Template.channel.helpers({
  channelName: function() {
    return Router.current().params.name;
  }
});

client/templates/views/channel/channel.js 

collections

#step-5

We need to store persistent data

  • Meteor uses MongoDB
Channels = new Mongo.Collection('channels');

Create a new Mongo Collection in lib/collections/channels.js:

Check out the different methods here

collections

#step-5

<template name="channelForm">
  <form>
    <div class="input-group">
      <input type="text" class="form-control" placeholder="channel name"
        name="channelName">
      <span class="input-group-btn">
        <button class="btn btn-default" type="submit">+</button>
      </span>
    </div>
  </form>
</template>

Create channel template:

client/templates/common/sidebar/channelForm/channelForm.html 

Include the template in the sidebar:

<template name="sidebar">
  <div class="col-sm-3 col-md-2 sidebar">
    <ul class="nav nav-sidebar">
      {{#each channels}}
        <li><a href="{{pathFor route='channel'}}"># {{name}}</a></li>
      {{/each}}
    </ul>
    {{> channelForm}}
  </div>
</template>

client/templates/common/sidebar/sidebar.html 

collections

#step-5

Template.channelForm.events({
  'submit form': function(event, instance) {
    event.preventDefault();

    var input = event.target.channelName;
    var name = input.value.trim().split(' ')[0];
    input.value = '';

    if (name !== '') {
      var channel = Channels.findOne({
        name: name
      });

      if (typeof channel === 'undefined') {
        Channels.insert({
          name: name
        });
      }
    }
  }
});

Insert channel on submit:

client/templates/common/sidebar/channelForm/channelForm.js 

collections

#step-5

Template.sidebar.helpers({
  channels: function() {
    return Channels.find();
  }
});

Change helper to return channels:

client/templates/common/sidebar/sidebar.js 

Template.channel.helpers({
  channel: function() {
    return Channels.findOne({
      name: Router.current().params.name
    });
  }
});

Change helper to return channel:

client/templates/common/channel/channel.js 

<template name="channel">
  <div class="page-header">
    <h1>{{channel.name}}</h1>
  </div>
</template>

client/templates/common/channel/channel.html 

collections

#step-5

How do I erase the local database?

meteor --help
meteor reset
meteor add msavin:mongol

Add the Mongol package!!!

Jetsetter is another package for inspecting Session variables. These packages are 'debug only'; they are not bundled for production. 

messages

#step-6

Messages = new Mongo.Collection('messages');

Create a new collection in lib/collections/messages.js:

Add message templates:

<template name="channel">
  <div class="page-header">
    <h1>{{channel.name}}</h1>
  </div>
  {{> messages}}
  {{> messageForm}}
</template>

client/templates/views/channel/channel.html 

messages

#step-6

<template name="messages">
  <ul class="list-unstyled">
    {{#each messages}}
      <li>{{> message}}</li>
    {{/each}}
  </ul>
</template>

client/templates/views/channel/messages/messages.html 

<template name="messageForm">
  <form>
    <div class="input-group">
      <input type="text" class="form-control" placeholder="message">
      <span class="input-group-btn">
        <button class="btn btn-default" type="submit">+</button>
      </span>
    </div>
  </form>
</template>

client/templates/views/channel/messageForm/messageForm.html 

<template name="message">
</template>

client/templates/views/channel/messages/message/message.html 

messages

#step-6

Insert message on submit:

Template.messageForm.events({
  'submit form': function(event, instance) {
    event.preventDefault();

    var input = event.target.content;
    var content = input.value.trim();
    input.value = '';

    if (content !== '') {
      Messages.insert({
        userId: Meteor.userId(),
        channel: Router.current().params.name,
        content: content,
        createdAt: new Date()
      });
    }
  }
});

client/templates/views/channel/messageForm/messageForm.html 

messages

#step-6

Add helper for messages:

Template.messages.helpers({
  messages: function() {
    return Messages.find({
      channel: Router.current().params.name
    });
  }
});

client/templates/views/channel/messages/messages.js 

Add message fields:

<template name="message">
  <div class="message">
    <span class="username">
      {{username}}
    </span>
    <span class="created-at">
      {{createdAt}}
    </span>
    <p class="content">{{content}}</p>
  </div>
</template>

client/templates/views/channel/messages/message/message.html 

messages

#step-6

Add helper for username:

Template.message.helpers({
  username: function() {
    return Meteor.users.findOne(this.userId, {
      username: true
    }).username;
  }
});

client/templates/views/channel/messages/message/message.js 

Bold username:

div.message > span.username {
  font-weight: bold;
}

client/templates/views/channel/messages/message/message.css 

messages

#step-6

Use Moment.js to format date:

<template name="message">
  <div class="message">
    <span class="username">
      {{username}}
    </span>
    <span class="datetime">
      {{datetime}}
    </span>
    <p class="content">{{content}}</p>
  </div>
</template>

client/templates/views/channel/messages/message/message.html 

meteor add momentjs:moment
...
datetime: function() {
  return moment(this.createdAt).format('MMMM Do, h:mm A');
}

client/templates/views/channel/messages/message/message.js 

deploy

Site will be hosted at http://myappname.meteor.com/

meteor deploy myappname

moving forward

  • Input validation
  • Publications/subscriptions
  • Allow/deny rules
  • Meteor methods
  • Projection
  • Denormalization

insecure

  • Grants all clients write access
  • Added by default
  • Useful for prototyping
  • Eventually you will want to remove this package

autopublish

  • Sends all documents to all clients!
  • Define access control with publications and subscriptions
// Server
Meteor.publish('channels', function() {
  return Channels.find({
    userIds: {
      $in: [this.userId]
    }
  });
});

// Client
Meteor.subscribe('channels');

packages

resources

Meteor

MongoDB

resources

JavaScript

Miscellaneous

tools

blym

#NOC #OnePiece #playstation

#life

Made with Slides.com