The Room Today
Wifi:
galvanize-guest- seattle
LevelUp!
The JavaScript App Platform
Build apps that are a delight to use, faster than you ever thought possible
You need an app ...
iOS or Android?!
Web
Watch / Small Devices
Sensors
Large Displays
We would need:
Meteor
Angular
Meteor
React
curl https://install.meteor.com/ | sh
OS X / Linux
Windows
InstallMeteor.exe
> meteor create my-app
> cd my-app
> meteor run
Other Folders
Initial Structure
<template name='myTemplateName'>
<!-- HTML goes here -->
</template>
if(Meteor.isClient) {
/*
Add my JS code here
*/
}
if(Meteor.isServer) {
/*
Add my JS code here
*/
}
<template name='myTemplateName'>
<button id='clickme'>Click Me</button>
</template>
//JS
Template.myTemplate.events({
'click #clickme': function(e,t) {
alert('i was clicked');
}
})
<template name='myTemplateName'>
<button id='clickme'>{{buttonName}}</button>
</template>
//JS
Template.myTemplate.helpers({
'buttonName': function() {
return 'Click Me Now';
}
})
myCollection = new Mongo.Collection('mycollection');
myCollection.insert({names: 'my name'});
> meteor deploy my-app.meteor.com
> meteor add-platform ios
> meteor run --mobile-server <IP/cname> ios-device
> meteor install-sdk android
# Install Java (web install)
# Install Emulator (web install)
> meteor add-platform android
> meteor run --mobile-server <IP/cname> android-device
YES! - Connect over DDP
Step 1: Create First Meteor App
Step 2: Live Coding
Step 3: Battle Cats
Step 4: Create Mobile App
<head>
<meta name="viewport" content="initial-scale=1, user-scalable=0">
<title>battle-cats-demo</title>
</head>
<body>
{{> main }}
</body>
<template name="main">
<h1>Battle Cats!</h1>
<h3>Pick a Cat:</h3>
{{#with cats}}
<img id='cat1' class='catChoice' src="/kittens/{{cat1}}.jpg" cat="{{cat1}}">
<img id='cat2' class="catChoice" src="/kittens/{{cat2}}.jpg" cat="{{cat2}}">
{{/with}}
</template>
if (Meteor.isClient) {
var cat1, cat2;
Template.main.helpers({
'cats': function () {
console.log('retrieving cats');
if (!cat1) {
cat1 = chooseRandomNumber(0, 13);
console.log('Loading cat1: ', cat1);
}
if (!cat2) {
cat2 = chooseRandomNumber(0, 13, cat1);
console.log('Loading cat2: ', cat2);
}
return {cat1: cat1, cat2: cat2};
}
});
}
function chooseRandomNumber(min, max, notEqualTo) {
var randomNumber = Math.floor(Math.random() * (max - min + 1)) + min;
if (!notEqualTo) {
return randomNumber;
}
if (randomNumber != notEqualTo) {
return randomNumber;
} else {
return chooseRandomNumber(min, max, notEqualTo);
}
}
<template name="main">
<h1>Battle Cats!</h1>
<h2>Battle # {{battleNumber}}: Pick a Cat</h2>
{{#with cats}}
<img id='cat1' class='catChoice' src="/kittens/{{cat1}}.jpg" cat="{{cat1}}">
<img id='cat2' class="catChoice" src="/kittens/{{cat2}}.jpg" cat="{{cat2}}">
{{/with}}
</template>
CatStats = new Mongo.Collection('catstats');
CatWins = new Mongo.Collection('catwins');
if (Meteor.isClient) {
var cat1, cat2, selectedCat;
Template.main.helpers({
'cats': function () {
// Session variable causes helper to be reactive and forces helper to reload when variable changes
var currentBattle = Session.get('battleNumber');
if (!cat1) {
cat1 = chooseRandomNumber(0, 13);
}
if (!cat2) {
cat2 = chooseRandomNumber(0, 13, cat1);
}
return { cat1: cat1, cat2: cat2 };
},
'battleNumber': function () {
return Session.get('battleNumber');
},
'battleResults': function () {
return CatWins.find({}, { sort: { numberWins: -1 }});
}
});
Template.main.events({
'click .catChoice': function (e, t) {
var elem = e.currentTarget;
//data-cat was not updating the winningCat, had to revert to attribute
var winningCat = $(elem).attr('cat');
if (selectedCat) {
return
} else {
selectedCat = true;
}
CatStats.insert({
catName: winningCat,
battleNumber: Session.get('battleNumber')
});
var catWinsDoc = CatWins.findOne({ catName: winningCat });
if (!catWinsDoc) {
CatWins.insert({ catName: winningCat, numberWins: 1 });
} else if (catWinsDoc) {
CatWins.update({ _id: catWinsDoc._id }, { $inc: { numberWins: 1 }});
}
var id = $(elem).attr('id');
var winningCatNum;
if (id === 'cat1') {
winningCatNum = 1
} else if (id === 'cat2') {
winningCatNum = 2
}
$('#results').text('Cat ' + winningCatNum + ' Wins! Loading Next Battle ...');
setTimeout(function () {
$('#results').text('');
selectedCat = false;
cat1 = null;
cat2 = null;
Session.set('battleNumber', Session.get('battleNumber') + 1);
}, 1000)
}
});
Meteor.startup(function () {
Session.set('battleNumber', 1);
});
}
function chooseRandomNumber(min, max, notEqualTo) {
var randomNumber = Math.floor(Math.random() * (max - min + 1)) + min;
if (!notEqualTo) {
return randomNumber;
}
if (randomNumber != notEqualTo) {
return randomNumber;
} else {
return chooseRandomNumber(min, max, notEqualTo);
}
}
<head>
<meta name="viewport" content="initial-scale=1, user-scalable=0">
<title>battle-cats-demo</title>
</head>
<body>
{{> header}}
{{> sAlert}}
{{> connectionBanner}}
<div id="main-segment" class="ui segment">
<div class="ui grid">
<div class="ui eleven wide column">
<div id="battle-segment" class="ui segment">
{{> catImages}}
</div>
<h3 class="ui header">Players</h3>
{{> numberOfPlayers}}
</div>
<div class="ui five wide column">
<h4 class="ui center aligned header">Leaderboard</h4>
{{> catBattleResults}}
</div>
</div>
</div>
</body>
<template name="header">
<div class="ui fixed inverted menu">
<div class="container">
<div class="title item">Battle Cats!</div>
<div class="right menu">
<div class="ui item">
<i class="user icon"></i>
{{playerCount}}
</div>
<div class="ui item">
<i class="thumbs up icon"></i>
{{voteCount}}
</div>
</div>
</div>
</div>
</template>
<template name="catImages">
<h3 class="ui header">Battle #{{battleNumber}}: Pick a cat!</h3>
<div id='results'></div>
<div class="ui center aligned">
<div class="ui medium image">
<img id='cat1' class='catChoice' src="/kittens/{{cats.cat1}}.jpg" cat="{{cats.cat1}}">
{{> loadingCat}}
</div>
<div class="ui medium image">
<img id='cat2' class="catChoice" src="/kittens/{{cats.cat2}}.jpg" cat="{{cats.cat2}}">
{{> loadingCat}}
</div>
</div>
</template>
<template name='catBattleResults'>
{{#each battleResults}}
<div class="ui card cat-card">
<div class="ui fluid image">
<div class="ui green right corner label">{{numberWins}}</div>
<img src="/kittens/{{catName}}.jpg"/>
</div>
<div class="extra content">
<span>Cat # {{catName}}</span>
</div>
</div>
{{/each}}
</template>
<template name='numberOfPlayers'>
{{#each activePlayers}}
<div class="ui card player-card">
<div class="content">
<span>
<i class="user icon"></i>
{{sessionId}}
</span>
</div>
<div class="extra content">
<span>
<i class="thumbs up icon"></i>
Votes
</span>
<span class="ui right floated header">
{{playerVotes sessionId}}
</span>
</div>
</div>
{{/each}}
</template>
<template name="loadingCat">
<div class="ui dimmer cat-loader">
<div class="ui small text loader">Loading cat...</div>
</div>
</template>
CatStats = new Mongo.Collection('catstats');
ActivePlayers = new Mongo.Collection('activeplayers');
CatWins = new Mongo.Collection('catwins');
if (Meteor.isClient) {
var cat1, cat2;
var selectedCat = false;
Template.header.helpers({
'playerCount': function () {
return ActivePlayers.find().count();
},
'voteCount': function () {
return CatStats.find().count();
}
});
Template.catImages.events({
'click .catChoice': function (e, t) {
var elem = e.currentTarget;
//data-cat was not updating the winningCat, had to revert to attribute
var winningCat = $(elem).attr('cat');
if (selectedCat) {
return
} else {
selectedCat = true;
}
CatStats.insert({
catName: winningCat,
sessionId: Session.get('sessionId'),
battleNumber: Session.get('battleNumber')
});
var catWinsDoc = CatWins.findOne({catName: winningCat});
if (!catWinsDoc) {
CatWins.insert({catName: winningCat, numberWins: 1});
} else if (catWinsDoc) {
CatWins.update({_id: catWinsDoc._id}, {$inc: {numberWins: 1}});
}
var id = $(elem).attr('id');
var winningCatNum;
if (id === 'cat1') {
winningCatNum = 1
} else if (id === 'cat2') {
winningCatNum = 2
}
$('#results').text('Cat ' + winningCatNum + ' Wins! Loading Next Battle ...');
$('.cat-loader').addClass('active');
setTimeout(function () {
$('#results').text('');
$('.cat-loader').removeClass('active');
selectedCat = false;
cat1 = null;
cat2 = null;
Session.set('battleNumber', Session.get('battleNumber') + 1);
}, 1000)
}
});
Template.catImages.helpers({
'cats': function () {
var currentBattle = Session.get('battleNumber');
if (!cat1) {
cat1 = chooseRandomNumber(0, 13);
}
if (!cat2) {
cat2 = chooseRandomNumber(0, 13, cat1);
}
return {cat1: cat1, cat2: cat2};
},
'battleNumber': function () {
return Session.get('battleNumber');
}
});
Template.catBattleResults.helpers({
'battleResults': function () {
return CatWins.find({}, {sort: {numberWins: -1}});
}
});
Template.numberOfPlayers.helpers({
'activePlayers': function () {
return ActivePlayers.find({status: 'active', sessionId: {$exists: true}});
},
'playerVotes': function (sessionId) {
if (!sessionId) {
return;
}
return CatStats.find({sessionId: sessionId}).count();
}
});
Meteor.startup(function () {
var sessionId = Random.id();
Session.set('sessionId', sessionId);
Session.set('battleNumber', 1);
Meteor.call('registerClient', sessionId, function (error, result) {
});
NotifyClient.register();
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
ActivePlayers.remove({});
});
Meteor.onConnection(function (connection) {
var userAgent = connection.httpHeaders['user-agent'];
ActivePlayers.insert({
connectionId: connection.id,
status: 'active',
userAgent: userAgent,
browser: getBrowser(userAgent),
provider: 'server'
});
connection.onClose(function () {
ActivePlayers.update({connectionId: connection.id}, {$set: {status: 'disconnected'}});
});
});
Meteor.methods({
'registerClient': function (sessionId) {
ActivePlayers.upsert({connectionId: this.connection.id}, {
$set: {
sessionId: sessionId,
connectionId: this.connection.id
}
});
}
})
}
function chooseRandomNumber(min, max, notEqualTo) {
var randomNumber = Math.floor(Math.random() * (max - min + 1)) + min;
if (!notEqualTo) {
return randomNumber;
}
if (randomNumber != notEqualTo) {
return randomNumber;
} else {
return chooseRandomNumber(min, max, notEqualTo);
}
}
function getBrowser(sUsrAg) {
var sBrowser;
if (sUsrAg.indexOf("Android") > -1) {
sBrowser = "Android OS"
} else if (sUsrAg.indexOf('iPad')) {
sBrowser = "iPad";
} else if (sUsrAg.indexOf('iPhone')) {
sBrowser = 'iPhone';
} else if (sUsrAg.indexOf("Chrome") > -1) {
sBrowser = "Google Chrome";
} else if (sUsrAg.indexOf("Safari") > -1) {
sBrowser = "Apple Safari";
} else if (sUsrAg.indexOf("Opera") > -1) {
sBrowser = "Opera";
} else if (sUsrAg.indexOf("Firefox") > -1) {
sBrowser = "Mozilla Firefox";
} else if (sUsrAg.indexOf("MSIE") > -1) {
sBrowser = "Microsoft Internet Explorer";
}
return sBrowser;
}
Join In: http://bit.ly/1N1OaPP
Meteor's greatest win is JavaScript everywhere: client, server,
database. Our team is all de facto full-stack developers.
Adam Monsen - Head of Engineering
Meteor has allowed us to reduce code needed by 4-10x over conventional techniques. Thanks to the in browser replicated DB, client side code has greatly reduced complexity, is mostly free from locking and is very reactive to user input. This makes it much faster to port our existing legacy applications to the web. As and example, in our legacy program it takes on average 100-200 lines to query the db, serialize and transmit it to the client, then bind that data on the dropdown and then report back to the server the users choice. In Meteor, its <10 lines, a DB query for the content, and a db update for the users choice
Nick Burrows - Coolearth Software
Our original app, which was a hybrid app that only worked on iOS, took more than 15 months to build, was still not complete, and was buggy and difficult to maintain. The new app was built from scratch using Meteor in less than 6 months, has significantly more functionality than the original, supports Android & iOS and is much easier to maintain.
Bryan Terrell - FlyBuy