You are on page 1of 23

1

Table of Contents
1. Introduction.................................................................................................................... 3
What is Meteor Streams.......................................................................................................... 3
What's in this eBook................................................................................................................. 3
Source Code................................................................................................................................. 3
2. Creating the App............................................................................................................ 5
Adding Meteor Streams........................................................................................................... 5
With Atmosphere.................................................................................................................................... 5
Using Git..................................................................................................................................................... 5
Adding Meteor Router.............................................................................................................
Adding hammer.!s.....................................................................................................................
3. Basic "I and Routes...................................................................................................... #
$et's add the "I........................................................................................................................... #
Adding Routes............................................................................................................................ %
&. $et's 'ui(d the B(ack'oard....................................................................................... 1)
5. *esigning the App +or Meteor Streams................................................................ 12
B(ack'oard app and Stream e,ents.................................................................................. 12
-ermissions.............................................................................................................................. 13
. Adding Meteor Streams Support........................................................................... 1&
Integrating B(ack'oard .-ad/............................................................................................. 1&
#. Adding 0ine12rade -ermissions............................................................................ 1#
Wh3 4e need permissions................................................................................................... 1#
$et's do the modi5ications.................................................................................................... 1#
6hank 7ou.......................................................................................................................... 1%
2
1. Introduction
I hope you have tried the Rea(time B(ack'oard application that I've created. If not,
please watch this ,ideo and tr3 it 3ourse(+. As the name implies, it is a realtime
blackboard where multiple users can draw and share on a common blackboard.
This proect is based on Alan !haw"s Meteor B(ack'oard. #e used a $eteor
Collection to add realtime behavior to his proect. That means that every pi%el you
draw on the client needs to &o throu&h mon&odb before it reaches other connected
clients. It works, but it is not the best approach for this kind of problem.
'or my (ealtime )lackboard app, I used a different approach to add the realtime
capabilities. Instead of &oin& throu&h mon&odb, I choose Meteor Streams to add
realtime communication to the app.
*hat is $eteor !treams
$eteor !treams is a proect created to add messa&in&+based communication to
$eteor. ,nce you've created a !tream, clients can pass messa&es back and forth.
Those messa&es are not routed throu&h mon&odb. $eteor !treams has a permission+
-
based securit3 mode(8 and throu&h 5i(ters8 you can control what clients are
communicatin&.
To follow this e)ook, you don't need prior e%perience with $eteor !treams. )ut try
havin& a look at Meteor Stream documentation before you proceed.
*hat's in this e)ook
*ith this e)ook, I will &uide you in buildin& the (ealtime )lackboard app usin&
$eteor !treams. I will assume you have some e%perience with $eteor already. If not,
read the *isco,er Meteor book.
!ource Code
!ource code for the application we are creatin& is available on the .ithub. Click
here to &et it.
At the end of each chapter, you can view and download the source code with the
chan&es made in that chapter and previous chapters.
!o why wait/ 0et"s &et started1
2
2. Creatin& the App
As usual, you need to create a meteor app and remove default packa&es and
unwanted files. 3ou need to add the bootstrap packa&e, too. 0et"s name our proect
awesome-blackboard.
meteor create awesome-blackboard
cd awesome-blackboard
meteor remove insecure autopublish
meteor add bootstrap
rm awesome-blackboard.* #remove files added by default
Addin& $eteor !treams
4ow it's time to incorporate $eteor !treams into our proect. $eteor !treams
distributes itself as a !mart 5acka&e, so you can very easily add it to the proect.
There are two possible ways to add it6
*ith Atmosphere
Atmosphere is a community+mana&ed !mart 5acka&e repository. 3ou can install
atmosphere packa&es with meteorite. ,nce you've installed meteorite, installin&
$eteor !treams is ust a sin&le shell command.
mrt add streams
7sin& .it
If you are not a fan of meteorite and atmosphere, you can add $eteor !treams to
your app as a &it submodule.
mrt add streamsmkdir -p packages
#make sure you created the packages folder
git submodule add https://github.com/arunoda/meteor-streams.git
packages/streams
8
4ow you've added $eteor !treams support for your app. 9asy, wasn"t it/
Addin& $eteor (outer
*e will use the Router proect to add basic routin& capabilities to our app. To add it
to the proect, I'll use meteorite to install it from atmosphere. 3ou can also add it
usin& &it as shown above.
mrt add router
Addin& hammer.s
,ur app needs to work smoothly on every possible browser, includin& mobile
browsers. *e need some help from a third party+library. *e'll use hammer.!s to
support touch events. 0et's add it to our proect6
:ownload hammer.js jQuery version
Add downloaded file into client folder
3ou've now completed the first step of our ourney1
View Source Code Download Source Code
;
-. )asic 7I and (outes
In our blackboard app, users can create as many blackboards as they want. <*e use
the term pad internally to identify a blackboard.= 9ach blackboard has its own 7(0.
Also, if someone visits the root of the website <>=, he will be redirected to a new
blackboard.
!o, basically, we have 2 types of routes as shown below.
1. >
2. >6padId
padId can be any value, since we have no restrictions on it. )ut it normally contains a
random id &enerated with Random.id!.
0et's add the 7I
!ince our focus here is on !treams, we don't need to discuss much about #T$0 and
C!!. It is minimal and strai&htforward. !o let's add it to the app.
Add following html into a file named client/home.html
"head#
"title#Realtime $lackboard with %eteor &treams"/title#
"/head#
"body#
"'-- (here router render the content --#
))render*age++
"/body#
4ow let's add followin& css into client/blackboard.css
body )
margin: ,-
overflow: hidden-
background-color: black-
+
#header )
position: fi.ed-
top: ,p.-
?
left: ,p.-
color: rgb//,0 //,0 //,!-
border-bottom: 1p. solid rgb1,,0 1,,0 1,,!-
padding: 12p. 2p. 2p. 1,p.-
width: 1,,3-
+
#header #heading )
float: left-
width: 4,,p.-
+
#header #controls )
float: right-
width: 5,,p.-
te.t-align: right-
padding-right: /,p.-
+
#header #heading h1 )
font-si6e: /7p.-
line-height: /2p.-
margin: ,p. ,p. 7p. ,p.-
float: left-
+
#header #heading h/ )
margin-bottom: 1,p.-
font-si6e: 15p.-
line-height: 15p.-
font-weight: normal-
+
#header #heading h/ a )
color: inherit-
te.t-decoration: none-
font-weight: bold-
border-bottom: 1p. dashed white-
+
.nickname )
position: absolute-
font-family: 89rial8-
padding: 1p. :p. 1p. :p.-
font-si6e: 14p.-
border-radius: 4p.-
border: /p. solid rgb1,,0 1,,0 1,,!-
color: white-
background-color: black-
+
@
*e have a sin&le template named pad that the blackboard is rendered into. Add the
followin& content to client/view/pad.html
"template name;8pad8#
"canvas#"/canvas#
"div id;8header8#
"div id;8heading8#
"h1#Realtime $lackboard"/h1#
"/div#
"div id;8controls8#
"span id;8show-nickname8#<ello0 "b#user"/b#"/span#
"input class;8btn btn-info btn-small8 type;=button=
id;8set-nickname8 value;8>hange ?ickname8/#
"input class;8btn btn-info btn-small8 type;=button=
id;8wipe8 value;8>lear $ackboard8/#
"input class;8btn btn-success btn-small8 type;=button=
id;8create-new8 value;8>reate ?ew8/#
"/div#
"/div#
"script id;8tmpl-nickname8 type;=te.t/html=#
"span class;8nickname8#
nickname
"/span#
"/script#
"/template#
4ow that we've added all the client side static files we need, it"s time to focus on the
routin&.
Addin& (outes
Add followin& content as client/routes.@s
%eteor.Router.add)
8/8: function! )
var new*adId ; Random.id!-
location.href ; 8/8 A new*adId-
+0

8/:padId8: )
as: 8pad80
to: functionpadId! )
&ession.set8padId80 padId!-
return 8pad8
+
+
+!-
A
The first route is simple. It will create a new padId and redirect to the second route.
The second route is a named route, named pad. It will set the padId, &iven in the
7(0, to the !ession, where the rest of our app can read it reactively.
View Source Code Download Source Code
1B
2. 0et's build the )lackboard
4ow we have the 7I placed into our app. 0et's build the blackboard by inte&ratin&
both hammer.s and the Cava!cript canvas A5I. A&ain, I'm not &oin& to e%plain each
and every step I made. )ut the code is easy to understand.
I've created a small library, which allows drawin& on a canvas by mouse dra&&in& or
touchin& the screen. It is an e%tended version of the ori&inal $eteor )lackboard
proect.
Add the followin& content into lib/pad.@s
if'%eteor.is>lient! return-
this.*ad ; function *adid! )
var canvas ; B8canvas8!-
var ct. ; canvasC,D.get>onte.t8/d8!-
var drawing ; false-
var from-
var skip>ount ; ,-
var nickname-
var color-

set?icknamelocal&torage.getItem8nickname8! EE Random.id!!-

var pad ; canvas.attr)
width: Bwindow!.width!0
height: Bwindow!.height!
+!.hammer!

//hammer.@s touch events
pad.on8dragstart80 onFrag&tart!-
pad.on8dragend80 onFragGnd!-
pad.on8drag80 onFrag!-

function onFragevent! )
ifdrawing! )
var to ; get*ositionevent!-
drawHinefrom0 to0 color!-
from ; to-
skip>ount ; ,-
+
+

function onFrag&tartevent! )
drawing ; true-
from ; get*ositionevent!-
+

function onFragGnd! )
11
drawing ; false-
+

function get*ositionevent! )
return )
.: parseIntevent.gesture.center.pageI!0
y: parseIntevent.gesture.center.pageJ!
+-
+

function drawHinefrom0 to0 color! )
ct..stroke&tyle ; color-
ct..begin*ath!-
ct..moveKofrom..0 from.y!-
ct..lineKoto..0 to.y!-
ct..close*ath!-
ct..stroke!-
+

function set?icknamename! )
nickname ; name-
B8#show-nickname b8!.te.tnickname!-
local&torage.setItem8nickname80 nickname!-

color ; local&torage.getItem8color-8 A nickname!-
if'color! )
color ; getRandom>olor!-
local&torage.setItem8color-8 A nickname0 color!-
+
+

function wipeemit9lso! )
ct..fillRect,0 ,0 canvas.width!0 canvas.height!!-
+

ct..stroke&tyle ; color-
ct..fill&tyle ; 8#,,,,,,8-
ct..line>ap ; 8round8-
ct..line(idth ; 4-

ct..fillRect,0 ,0 canvas.width!0 canvas.height!!-

// &top iL& from doing the bounce thing with the screen
document.ontouchmove ; functionevent!)
event.preventFefault!-
+

//e.pose 9*I
this.drawHine ; drawHine-
this.wipe ; wipe-
this.set?ickname ; set?ickname-

this.close ; function! )
pad.off8dragstart80 onFrag&tart!-
pad.off8dragend80 onFragGnd!-
pad.off8drag80 onFrag!-
+-
12
+
function getRandom>olor! )
var letters ; 8,1/4:25M7N9$>FGO8.split88!-
var color ; 8#8-
for var i ; ,- i " 5- iAA ! )
color A; lettersC%ath.round%ath.random! * 12!D-
+
return color-
+
4ow it is time to inte&rate the above *ad class with our routes. To do that, add
followin& content into client/views/pad.@s
var pad-
%eteor.startupfunction! )
Feps.autorunfunction! )
ifpad! )
pad.close!-
+
var padId ; &ession.get8padId8!-
pad ; new *adpadId!-
+!-
+!-
*henever the router sets a padId, the above code will take care of closin& the e%istin&
pad and create a new one.
#ave you noticed the - buttons we have in the ri&ht top corner/ 0et's add their
functionality.
Append the followin& content to client/views/pad.@s.
Bfunction! )
//>lear $lackboard
B8body8!.on8click80 8#wipe80 function! )
pad.wipetrue!-
+!-
//>hange ?ickname
B8body8!.on8click80 8#set-nickname80 function! )
var name ; prompt8Gnter your nickname8!-
ifname PP name.trim! '; 88! )
pad.set?icknamename!-
+
+!-

1-
//>reate ?ew
B8body8!.on8click80 8#create-new80 function! )
var new*adId ; Random.id!-
%eteor.Router.to8pad80 new*adId!-
+!-
+!-
4ow we've a fully functionin& blackboard. .ive it a try.
In the ne%t section you'll be startin& to inte&rate Meteor Streams with the
Blackboard and make it realtime.
View Source Code Download Source Code
12
8. :esi&nin& the App for $eteor
!treams
4ow we have a fully functionin& blackboard app. )ut it is not realtime and cannot be
shared with anyone. In this section I'll show you how to desi&n our app for $eteor
!treams.
If you don't have a workin& blackboard app <maybe you've decided to directly ump
into this chapter=, do4n(oad it +rom here.
%eteor.&tream is the class e%posed by $eteor !treams, which allows you to create a
!tream. It is a realtime EentEmitter and works across the $eteor. 3ou can pass
messa&es between clients. 3ou can even send and listen to the messa&es on the
!erver.
)lackboard app and !tream events
In our blackboard app, when a user is drawin& somethin& on it, we need to send what
he is drawin& to all other connected users. !o we need to send followin& information6
0ocation where the mouse dra&&in& <or touchin& on the screen= started
0ocations the user is currently dra&&in&
0ocation where the dra&&in& has ended
4ickname and the color of the pencil
*e'll use these three events to communication and listen to the above information.
dra&start <also sends the nickname and the color=
dra&
dra&end
)ut we have multiple pads in a sin&le app, so we need to namespace the above events
with the padId as shown below. 0et's assume our padId is H:n?Mr7OpQ5.gpGiQ.
02n4?r@'pC;%&p9iC6dra&start
02n4?r@'pC;%&p9iC6dra&
02n4?r@'pC;%&p9iC6dra&end
18
5ermissions
)y default, clients don't have read or write access to the !tream. *e need to e%plicitly
enable permissions. *e can use event name, userId and subscriptionId to make
the decision to allow or deny conditionally.
(ead more on $eteor !treams securit3 and permissions
'or simplicity, we'll enable clients to communicate without any restrictions at first.
)ut in the later chapters, we'll discuss more on advanced permissions, which make
the app more efficient.
0et's start inte&ratin& $eteor !treams1
1;
;. Addin& $eteor !treams !upport
*e will be usin& a sin&le !tream and it needs to be created on both client and server.
*hen creatin& the stream, we need to use the same name for it in both places. It is
not the variable name, but the name of the stream passed as the first parameter.
0et's add the followin& code in streams.@s.
Hine&tream ; new %eteor.&tream8lines8!-
if%eteor.is&erver! )
Hine&tream.permissions.readfunction! )
return true-
+!-
Hine&tream.permissions.writefunction! )
return true-
+!-
+
It will create a !tream in both client and server and add permissions. #ere we simply
add no restrictions to the stream. It"s not ideal, but we'll catch up with this later.
Inte&ratin& )lackboard <5ad=
4ow we need to emit events when a user draws somethin& so others can &et those
events and make them visible on their screens. !ee followin& diff of the lib/pad.@s
where you need to make chan&es.

ifdrawing! )
var to ; get*ositionevent!-
drawHinefrom0 to0 color!-
A Hine&tream.emitid A 8:drag80 nickname0 to!-
from ; to-
skip>ount ; ,-
+
RR -4401, A4401/ RR this.*ad ; function *adid! )
function onFrag&tartevent! )
drawing ; true-
from ; get*ositionevent!-
A Hine&tream.emitid A 8:dragstart80 nickname0 from0 color!-
+
1?

function onFragGnd! )
drawing ; false-
A Hine&tream.emitid A 8:dragend80 nickname!-
+
function get*ositionevent! )
RR -5505 A570N RR this.*ad ; function *adid! )
function wipeemit9lso! )
ct..fillRect,0 ,0 canvas.width!0 canvas.height!!-
A ifemit9lso! )
A Hine&tream.emitid A 8:wipe80 nickname!-
A +
+
Click here to view the full file if you are havin& difficulty understandin& the above
diff.
4ow we are emittin& events, so the ne%t step is to listen to those events and render
them onto the screen. To do that, I've created a class called Remote*ad. I've also
added inline comments for you to understand it Duickly.
Add the followin& content to lib/remoteSpad.@s
if'%eteor.is>lient! return-
this.Remote*ad ; function Remote*adpadId0 pad! )
var users ; )+-
//listening on the dragstart event for the given padId
Hine&tream.onpadId A 8:dragstart80 functionnickname0 position0
color! )
//display the nickname pointer on the screen as remote user draws
on the pad
var pointer ; BB8#tmpl-nickname8!.te.t!!-
pointer.te.tnickname!-
position*ointerpointer0 position!-
B8body8!.appendpointer!-
usersCnicknameD ; )
color: color0
from: position0
pointer: pointer
+-
+!-

1@
//listening on the dragend event for the given padId
Hine&tream.onpadId A 8:dragend80 functionnickname! )
//cleaning at the dragend
var user ; usersCnicknameD-
ifuser! )
user.pointer.remove!-
usersCnicknameD ; undefined-
+
+!-
//listening on the drag event for the given padId
Hine&tream.onpadId A 8:drag80 functionnickname0 to! )
var user ; usersCnicknameD-
ifuser! )
//when remote user is dragging0 do the same here and re-position
the nickname pointer
pad.drawHineuser.from0 to0 user.color!-
position*ointeruser.pointer0 to!-
user.from ; to-
+
+!-
// listening on the wipe event and wipe the blackboard
Hine&tream.onpadId A 8:wipe80 functionnickname! )
pad.wipe!-
+!-
function position*ointerpointer0 position! )
pointer.css)
top: position.y A 1,0
left: position.. A 1,
+!-
+
this.close ; function! )
//remove all the listeners0 when closing
Hine&tream.remove9llHistenerspadId A 8:dragstart8!-
Hine&tream.remove9llHistenerspadId A 8:dragend8!-
Hine&tream.remove9llHistenerspadId A 8:drag8!-
Hine&tream.remove9llHistenerspadId A 8:wipe8!-
+-
+
1A
As the last step, we need to inte&rate Remote*ad when we are creatin& a route. !ee
followin& diff of the client/views/pad.@s for how you can do it.
RR -1014 A1015 RR
var pad-
Avar remote*ad-
%eteor.startupfunction! )
Feps.autorunfunction! )
ifpad! )
pad.close!-
A remote*ad.close!-
+
var padId ; &ession.get8padId8!-
pad ; new *adpadId!-
A remote*ad ; new Remote*adpadId0 pad!-
+!-
+!-
Click here to view the full file if you are havin& difficulty understandin& the above
diff.
,kay, now our blackboard is realtime and multiple users can draw on it at the same
time. 4ice1 To see that for yourself, open the same blackboard 7(0 in two different
browsers and see.
View Source Code Download Source Code
2B
?. Addin& 'ine+.rade 5ermissions
,ur blackboard app is now workin&, but it is not efficient. )ecause we have failed to
add permissions, that makes sense.
*hy we need permissions
0et's have a look at our app. It emits events and other listen to them. #ave you
noticed that althou&h we are only listenin& to the events for the current padId,
nothin& prevents receivin& events for other padIds as well.
This means that every event clients are emittin& will be delivered to each and every
client, re&ardless of the padId. !o we need to fi% that.
Ideally, clients should receive events belon&s to the padId, which they are currently
listenin& to. *e can easily do it with permissions.
1. 'irst, we need notify the server that client <client J= is only interested in
receivin& events for padId I.
2. In the server for client J, we must only send events related to padId I
In the ne%t section, you'll see that I've discussed subscription and
subscriptionId. A subscription is created when a client <browser tab= is connected
to a stream. $eteor !treams creates a uniDue id for each subscription and it is the
subscriptionId
0et's do the modifications
'irst, we need to notify the server about the padId we are interested in. *e need to
modify lib/pad.@s for that. The followin& diff e%plains how to do it.
RR -1,05 A1,0N RR this.*ad ; function *adid! )
var color-
set?icknamelocal&torage.getItem8nickname8! EE Random.id!!-
A
A //send padid to the sever
A Hine&tream.emit8pad80 id!-
21
var pad ; canvas.attr)
width: Bwindow!.width!0
Then we need to listen to the pad event on the server and update our permissions. To
do that, you need to replace streams.@s with the followin& content. I've added
inline comments to make it more understandable.
Hine&tream ; new %eteor.&tream8lines8!-
if%eteor.is&erver! )
//map subscriptionId and the padId he is listening to
var subscription*ads%ap ; )+-
Hine&tream.on8pad80 functionpadId! )
var subscriptionId ; this.subscriptionId-
subscription*ads%apCsubscriptionIdD ; padId-
this.onFisconnect ; function! )
subscription*ads%apCsubscriptionIdD ; undefined-
+-
+!-
Hine&tream.permissions.readfunctionevent! )
//getting padId from the event
var matched ; event.match/.*!:/!-
ifmatched! )
var padId ; matchedC1D-
//only allow events with padId where subscription is
interestedIn
return subscription*ads%apCthis.subscriptionIdD ;; padId-
+ else )
//only allows events with padId to read from the stream
return false-
+
+0 false!- //end false make sure read permissons will not be cached.
&ee permission docs for more
Hine&tream.permissions.writefunctionevent! )
return true-
+!-
+
#ooray1 4ow our blackboard is fully functional and efficient. :eploy it and try with
your friends in realtime.
View Source Code Download Source Code
22
Thank 3ou
Thank you for takin& the time to read this e)ook. I"ve tried to make it simple and
strai&ht to the point. I hope you have enoyed it.
Try to build somethin& innovative usin& $eteor !treams. Try to use it with your ne%t
proect. $eteor !treams is a new proect, but I will continue to develop it ++ there"s a
lot to do.
If you encounter somethin& weird with $eteor !treams, create an issue for it. And I
would love to see your contributions. It could be a blo& post, sample app, an issue or a
pull reDuest.
#ave a .ood :ay1
Arunoda !usiripala
9arunoda
http:;;meteorhacks.com
2-

You might also like