SlideShare uma empresa Scribd logo
1 de 124
Baixar para ler offline
MODERN WEB APPLICATION
WITH METEOR
이재호
Appsoulute 대표
jhlee@appsoulute.com
http://github.com/acidsound
http://spectrumdig.blogspot.kr
INSTALL METEOR
Linux/OS X curl https://install.meteor.com/ | sh 

Windows https://install.meteor.com/windows
첫 METEOR APP
• meteor로 시작하는 명령은
터미널이나 커맨드라인(시작
>실행>cmd)에서 입력합니
다.
• meteor create sogon2x
APP 실행하기
• meteor run / meteor
구현 목표
관심사 기반 마이크로 블로깅 서비스
1. 화면생성
2. 포스트 입력
3. 이벤트 처리
4. 포스트 정렬
5. 사용자 계정
6. 구독/탈퇴
7. 대쉬보드
백문불여일타
(百聞不如一打)
한타 한타 시작해봅시다
TOOL
• 어떤 걸로 코드를 만드실 건
가요?
• ATOM (무료 추천!)
• Sublime text (인기!)
• Webstorm (유료 최고!)
JAVASCRIPT 구조
CLIENT
if (Meteor.isClient) {
}
SERVER
if (Meteor.isServer) {
Meteor.startup(function() {
// code to run on server at startup
});
}
사용자 

브라우저에서
실행합니다.
서버에서
실행합니다.
HTMLTEMPLATE
(mobile first!)
index.html
<head>
<title>sogon2x</title>
<meta name="viewport" content="width=device-
width, initial-scale=1">
</head>
<body>
{{> head}}
{{> main}}
</body>
head.html
<template name="head">
<h1>fixed header</h1>
</template>
main.html
<template name="main">
<p>context</p>
</template>
emmet- meta:vp
HEAD
• https://atmospherejs.com/twbs/bootstrap
• meteor add twbs:bootstrap
• 적용 후 변화를 관찰
• navbar 사용

http://bootstrapk.com/components/#navbar-brand-
image
HEAD - NAVBAR
<template name="head">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Sogon2X</a>
</div>
</div>
</nav>
</template>
HEAD - NAVBAR
<template name="main">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Sogon2X</a>
</div>
</div>
</nav>
</template> 상단 네비게이션 바
HEAD - NAVBAR
<template name="main">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Sogon2X</a>
</div>
</div>
</nav>
</template>
기본 색상

navbar-inverse도 시도
HEAD - NAVBAR
<template name="main">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Sogon2X</a>
</div>
</div>
</nav>
</template>
상단 고정 (optional)
HEAD - NAVBAR
<template name="main">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Sogon2X</a>
</div>
</div>
</nav>
</template>
컨테이너
http://bootstrapk.com/css/#overview-container
HEAD - NAVBAR
<template name="main">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Sogon2X</a>
</div>
</div>
</nav>
</template> Header 영역
HEAD - NAVBAR
<template name="main">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Sogon2X</a>
</div>
</div>
</nav>
</template> 로고 영역
MAINTEMPLATE
버튼 애드온을 사용하여 입력 창을 만듭니다.
http://bootstrapk.com/components/#input-groups-
buttons
MAINTEMPLATE
<div class="container">

<h2>Nobody's Page</h2>

<form>

<div class="input-group">

<input type="text" id="post" class="form-control" placeholder="Tell me something..."/>

<div class="input-group-btn">

<button class="btn btn-primary">

<i class="glyphicon glyphicon-pencil"></i>

Post

</button>

</div>

</div>

</form>

</div>
MAINTEMPLATE
<div class="container">

<h2>Nobody's Page</h2>

<form>

<div class="input-group">

<input type="text" id="post" class="form-control" placeholder="Tell me something..."/>

<div class="input-group-btn">

<button class="btn btn-primary">

<i class="glyphicon glyphicon-pencil"></i>

Post

</button>

</div>

</div>

</form>

</div>
입력 그룹

http://bootstrapk.com/components/#input-groups
MAINTEMPLATE
<div class="container">

<h2>Nobody's Page</h2>

<form>

<div class="input-group">

<input type="text" id="post" class="form-control" placeholder="Tell me something..."/>

<div class="input-group-btn">

<button class="btn btn-primary">

<i class="glyphicon glyphicon-pencil"></i>

Post

</button>

</div>

</div>

</form>

</div>
폼 요소
MAINTEMPLATE
<div class="container">

<h2>Nobody's Page</h2>

<form>

<div class="input-group">

<input type="text" id="post" class="form-control" placeholder="Tell me something..."/>

<div class="input-group-btn">

<button class="btn btn-primary">

<i class="glyphicon glyphicon-pencil"></i>

Post

</button>

</div>

</div>

</form>

</div>
버튼 애드온
http://bootstrapk.com/components/#input-groups-buttons
MAINTEMPLATE
<div class="container">

<h2>Nobody's Page</h2>

<form>

<div class="input-group">

<input type="text" id="post" class="form-control" placeholder="Tell me something..."/>

<div class="input-group-btn">

<button class="btn btn-primary">

<i class="glyphicon glyphicon-pencil"></i>

Post

</button>

</div>

</div>

</form>

</div>
버튼 옵션
http://bootstrapk.com/css/#buttons-options
MAINTEMPLATE
<div class="container">

<h2>Nobody's Page</h2>

<form>

<div class="input-group">

<input type="text" id="post" class="form-control" placeholder="Tell me something..."/>

<div class="input-group-btn">

<button class="btn btn-primary">

<i class="glyphicon glyphicon-pencil"></i>

Post

</button>

</div>

</div>

</form>

</div>
아이콘
http://bootstrapk.com/components/#glyphicons
POSTTEMPLATE
• Main 아래 Post들의 목록을 열거하는 화면구성
• media를 사용하여 UI를 먼저 만든다.
• http://bootstrapk.com/components/#media-default
POSTTEMPLATE
• main template 아래에 

{{> posts}} 를 추가하여
posts라는 템플릿을 붙여주
도록한다.
<template name="main">

<div class="container">
….
{{> posts}}
</div>
</template>
POSTTEMPLATE
• Main 아래 Post들의 목록을 열거하는 화면구성
• media를 사용하여 UI를 먼저 만든다.
• http://bootstrapk.com/components/#media-default
POSTTEMPLATE<template name="posts">

<div class="media">

<div class="media-left">

<a href="#">

<img class="media-object" src="http://lorempixel.com/64/64/cats/" alt="nobody">

</a>

</div>

<div class="media-body">

<h4 class="media-heading">Master</h4>

집사야 내 밥은 어디있냐?

</div>

</div>

<div class="media">

<div class="media-left">

<a href="#">

<img class="media-object" src="http://lorempixel.com/64/64/people/" alt="nobody">

</a>

</div>

<div class="media-body">

<h4 class="media-heading">Slave4U</h4>

배고파서 내가 먹었다.

</div>

</div>

</template>
POSTTEMPLATE<template name="posts">

<div class="media">

<div class="media-left">

<a href="#">

<img class="media-object" src="http://lorempixel.com/64/64/cats/" alt="nobody">

</a>

</div>

<div class="media-body">

<h4 class="media-heading">Master</h4>

집사야 내 밥은 어디있냐?

</div>

</div>

<div class="media">

<div class="media-left">

<a href="#">

<img class="media-object" src="http://lorempixel.com/64/64/people/" alt="nobody">

</a>

</div>

<div class="media-body">

<h4 class="media-heading">Slave4U</h4>

배고파서 내가 먹었다.

</div>

</div>

</template>
반복구간
POSTTEMPLATE
{{#each posts}}
….
{{/each}}
• 반복 구간 처리
이제 코딩을 합시다.
Let’s Do Some Coding!
일단 이사 먼저!
• client 폴더를 만듭니다.
• 지금까지 만든 모든 html파
일들을 client 아래로 이동합
니다.
• 같은 곳에 posts.js를 만들어
줍니다.
POSTTEMPLATE
가짜로 자료를 만듭니다.
posts.js 안에 반복 구간에 들어갈 값들을 JSON 형태
로 만들어봅시다.
POSTTEMPLATE
Template.posts.helpers({

"posts": function() {

return [

{

author: {

name: "Master",

profile_image: "http://lorempixel.com/64/64/cats/"

},

message: "집사야 내 밥은 어딨냐?"

},

{

author: {

name: "Slave4U",

profile_image: "http://lorempixel.com/64/64/people/"

},

message: "배고파서 내가 먹었다."

}

]

}

});
POSTTEMPLATE
• posts.html에 반복 구간을 정하고 값을 받을 helper
들로 교체합니다.
POSTTEMPLATE
<template name="posts">

<div class="media">

<div class="media-left">

<a href="#">

<img class="media-object" src="http://lorempixel.com/64/64/
cats/" alt="nobody">

</a>

</div>

<div class="media-body">

<h4 class="media-heading">Master</h4>

집사야 내 밥은 어디있냐?

</div>

</div>

</template>
POSTTEMPLATE<template name="posts">

{{#each posts}}

<div class="media">

<div class="media-left">

<a href="#">

<img class="media-object" src="http://lorempixel.com/64/64/cats/"
alt="nobody">

</a>

</div>

<div class="media-body">

<h4 class="media-heading">Master</h4>

집사야 내 밥은 어디있냐?

</div>

</div>

{{/each}}

</template>
POSTTEMPLATE<template name="posts">

{{#each posts}}

<div class="media">

<div class="media-left">

<a href="#">

<img class="media-object" src="{{author.profile_image}}"
alt="{{author.name}}">

</a>

</div>

<div class="media-body">

<h4 class=“media-heading”>{{author.name}}</h4>

{{message}}

</div>

</div>

{{/each}}

</template>
중간 결과물
Mobile과 Desktop
동일하게 나옵니까?
CONNECT DB
• lib/collection.js 에 추가

Posts = new Mongo.Collection('posts');
• client/server 양쪽에 적용
• 기존 posts.js 수정

Template.posts.helpers({

"posts": function() {

return Posts.find();

}

});
CONNECT DB
• Browser Console에서 테스트
• Posts.insert({

author: {

name: "Master",

profile_image: "http://lorempixel.com/64/64/cats/"

},

message: "집사야 내 밥은 어딨냐?"

});
• Posts.find().fetch();
• 화면과 결과값을 확인
SERVER
METHOD
보안이 필요한 시기
REMOVE INSECURE
• meteor remove insecure
• insert failed:Access denied
• 사용자가 임의로 데이터 조작을 할 수 없음
METHODS
• server/methods.js - 서버에서만 insert

Meteor.methods({

"addPosts": function(obj) {

Posts.insert({

author: {

name: obj.name,

profile_image: obj.profile_image

},

message: obj.message

});

}

});
44
CLIENT CALL
• Method.call 사용. 콘솔에서 테스트.

Meteor.call("addPosts", {

name: "Slave4U",

profile_image: "http://lorempixel.com/64/64/
people/",

message: "배고파서 내가 다 먹었다."

});
45
EVENT HANDLING
• Template.main.events({

"submit": function(event, template) {

Meteor.call("addPosts", {

name: "Slave4U",

profile_image: "http://lorempixel.com/64/64/people/",

message : template.find('#post').value

}, function(err, result) {

if (err) {

throw(error);

} else {

console.log(result);

template.find('#post').value = "";

}

});

event.preventDefault();

}

});
46
사용자 로그인과
연동 필요
EVENT HANDLING
• Template.main.events({

"submit": function(event, template) {

Meteor.call("addPosts", {

name: "Slave4U",

profile_image: "http://lorempixel.com/64/64/people/",

message : template.find('#post').value

}, function(err) {

if (err) {

throw(error);

} else {

console.log(result);

template.find('#post').value = "";

}

});

event.preventDefault();

}

});
47
템플릿 안에서 post라는
id를 가진 객체를 검색.
그 값을 가져온다.
EVENT HANDLING
• Template.main.events({

"submit": function(event, template) {

Meteor.call("addPosts", {

name: "Slave4U",

profile_image: "http://lorempixel.com/64/64/people/",

message : template.find('#post').value

}, function(err) {

if (err) {

throw(error);

} else {

template.find('#post').value = "";

}

});

event.preventDefault();

}

});
48
method call 후 오류처리
EVENT HANDLING
• Template.main.events({

"submit": function(event, template) {

Meteor.call("addPosts", {

name: "Slave4U",

profile_image: "http://lorempixel.com/64/64/people/",

message : template.find('#post').value

}, function(err) {

if (err) {

throw(error);

} else {

template.find('#post').value = "";

}

});

event.preventDefault();

}

});
49
처리 성공 후 입력창
내용 삭제
EVENT HANDLING
• Template.main.events({

"submit": function(event, template) {

Meteor.call("addPosts", {

name: "Slave4U",

profile_image: "http://lorempixel.com/64/64/people/",

message : template.find('#post').value

}, function(err) {

if (err) {

throw(error);

} else {

template.find('#post').value = "";

}

});

event.preventDefault();

}

});
50
기존 submit 이벤트를 금지
페이지 이동이 안되도록 제한
RESET DATABASE
• 서버 정지
• meteor reset
• 재기동
ADDPOSTS
• server/methods.js - 서버에서만 insert

Meteor.methods({

"addPosts": function(obj) {

Posts.insert({

author: {

name: obj.name,

profile_image: obj.profile_image

},

message: obj.message,

createdAt: new Date()

});

}

});
52
반드시 서버 시간!
SORT BYTIME DESC
• 시간 역순 정렬. Server 시간 기준
• http://docs.meteor.com/#/full/sortspecifiers
• Posts.find({}, {

sort: {

createdAt: -1

}

});
POSTS HELPER
• posts.js

Template.posts.helpers({

"posts": function() {

return Posts.find({}, {

sort: {

createdAt: -1

}

});

}

});
정렬순서

-1 : 내림차순
1 : 오름차순
SESSION
insecure처럼
편리하지만 버려야할 것
계륵(鷄肋)
…하지만 맛있다
SESSION
• Session의 장점

전역으로 사용할 수 있다.

브라우저 콘솔에서 사용이 자유롭다.

서버 재시작 이후에도 값을 유지한다.
• Session의 단점

전역으로 밖에 사용할 수 없다.

Deprecated 예정
SESSION 사용법
• Session의 읽기 

Session.get('pageId');
• Session의 쓰기

Session.set('pageId', 'catLover');
SESSION 적용
• main.js

Template.main.helpers({

'page': function() {

return Session.get('pageId');

}

});
• main.html

<template name="main">

<div class="container">

<h2>{{page}}'s Page</h2>

…
SESSION.SET
• 브라우저 콘솔에서

Session.set('pageId', 'catLover')
• 바로 화면이 갱신되는 것을 관찰
• 어째서 이렇게 될까?

Reactive Programming!

http://docs.meteor.com/#/full/reactivity
PUBLISH/SUBSCRIBE
• 보고싶은 것만 보고 싶어요.
• meteor remove autopublish
AUTOPUBLISH?
• insecure 처럼 기본 설치 Meteor package
• Collection의 모든 내용을 서버로부터 가져온다.
• 하지만 우리는 page별로 따로따로 보고 싶다.
BEFORE
default Autopublish
AFTER
with Publish/Subscribe
(https://
www.discovermeteor.com/
blog/understanding-meteor-
publications-and-
subscriptions/)
REMOVE AUTOPUBLISH
• meteor remove autopublish
• 어? 아무것도 안나와요?????
DON’T PANIC
• 원래대로 돌려놓아 봅시다.
• server/publish.js 추가

Meteor.publish('getPage', function() {

return Posts.find();

});
• 브라우저 콘솔에서 확인해보자

Meteor.subscribe('getPage');
MANUAL SUBSCRIPTION
• main.js에 subscribe 추가

Template.main.onCreated(function() {

this.subscribe('getPage');

});
• 원래대로 돌아왔다!
PUB/SUB BASIC
• Server에서 publish 한 데이터를...

Meteor.publish('publishName', function() {

return YourCollection.find();

});
• client에서 subscribe 에서 가져온다.

Template.yourTemplate.onCreated(function() {

this.subscribe('publishName');

});
• 간단하죠?
PUBLISH WITH PAGEID
• 조건을 주고 필요한 것들만 가져옵니다.

(http://docs.meteor.com/#/full/selectors)
• server/publish.js 수정

Meteor.publish('getPage', function(pageId) {

return Posts.find({pageId: pageId});

});
SUBSCRIBE WITH PAGEID
• client/main.js 수정

Template.main.helpers({

'page': function() {

return Session.get('pageId') || 'popular';

}

});
• client/posts.js 수정

Template.posts.onCreated(function() {

this.subscribe('getPage', Session.get('pageId'));

});
pageId가 없으면

popular를 기본으로
pageId로 가입
CALL WITH PAGEID
• client/main.js 수정

Template.main.events({

"submit": function(event, template) {

Meteor.call("addPosts", {

name: "Slave4U",

profile_image: "http://lorempixel.com/64/64/people/",

pageId: Session.get('pageId'),

message : template.find('#post').value

}, function(err) {

…
METHOD WITH PAGEID
• server/methods.js 수정

Meteor.methods({

"addPosts": function(obj) {

Posts.insert({

author: {

name: obj.name,

profile_image: obj.profile_image

},

pageId: obj.pageId,

message: obj.message,

createdAt: new Date()

});

}

})
ROUTER
어디로 가야하나요?
콘솔에서 Session.set은 그만
KEYWORD별 POSTS
• 같은 관심사를 가진 사람들끼리 이야기 할 수 있도록
POSTS를 분리
• 채널이나 대화방 같은 느낌
• Page라는 이름으로 분리
• URL로 구분

/page/keyword
ROUTING
• Routing용 package 설치
• meteor add kadira:flow-router
WARNING!
• Flow-router는 third-party package입니다.

작성자가 꼭 업데이트를 보증하지 않습니다.
• 어떤 Router를 사용할지는 선택할 수 있습니다.
• Single Page Application에서 Routing(URL 경로)가
꼭 필수이진 않습니다.
ROUTER 만들기
• https://kadira.io/academy/meteor-routing-guide/content/
introduction-to-flow-router
• client/router.js 생성 (원래 이렇게 쓰는 건 아니에요!)

FlowRouter.route('/page/:pageId', {

name: 'main',

action: function(params) {

Session.set('pageId', params.pageId);

}

});
인자를 받아서
Session에 기록한다.
ACCOUNTS
meteor add accounts-
password
사용자를 만들자
ACCOUNTS PACKAGE
• meteor add accounts-password
• http://docs.meteor.com/#/full/accounts_api
• Meteor.user() - 현재 접속중인 사용자
• Meteor.userId() - 접속 중인 사용자 ID
• Meteor.loginWithPassword(user, password, [callback]) 

로그인하기, 성공 시 callback function 실행
• Meteor.logout() - 로그아웃
• Accounts.createUser(option, [callback]) - 사용자 생성
ACCOUNTS PACKAGES
• meteor add accounts-
password



E-mail/password 인증
• meteor add ian:accounts-ui-
bootstrap-3



bootstrap3용 accounts UI
• Template에 {{> loginButtons}}
LOGINBUTTONS
<template name="head">

<nav class="navbar navbar-default navbar-static-top">

<div class="container">

<div class="navbar-header">

<a class="navbar-brand" href="#">Sogon2x</a>

<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"

data-target=".navbar-collapse">

<span class="icon-bar"></span>

<span class="icon-bar"></span>

<span class="icon-bar"></span>

</button>

</div>

<div class="navbar-collapse collapse">

<ul class="nav navbar-nav navbar-right">{{> loginButtons}}</ul>

</div>

</div>

</nav>

</template> https://github.com/ianmartorell/meteor-
accounts-ui-bootstrap-3/#how-to-use
LOGINBUTTONS
<template name="head">

<nav class="navbar navbar-default navbar-static-top">

<div class="container">

<div class="navbar-header">

<a class="navbar-brand" href="#">Sogon2x</a>

<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"

data-target=".navbar-collapse">

<span class="icon-bar"></span>

<span class="icon-bar"></span>

<span class="icon-bar"></span>

</button>

</div>

<div class="navbar-collapse collapse">

<ul class="nav navbar-nav navbar-right">{{> loginButtons}}</ul>

</div>

</div>

</nav>

</template>
모바일에서 접히는 영역
LOGINBUTTONS
<template name="head">

<nav class="navbar navbar-default navbar-static-top">

<div class="container">

<div class="navbar-header">

<a class="navbar-brand" href="#">Sogon2x</a>

<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"

data-target=".navbar-collapse">

<span class="icon-bar"></span>

<span class="icon-bar"></span>

<span class="icon-bar"></span>

</button>

</div>

<div class="navbar-collapse collapse">

<ul class="nav navbar-nav navbar-right">{{> loginButtons}}</ul>

</div>

</div>

</nav>

</template>
loginButtons 삽입 (MAGIC!!)
USERNAME
• 사용자명 추가
• https://github.com/ianmartorell/meteor-accounts-ui-bootstrap-3/#custom-signup-
options
• client/config.js

Accounts.ui.config({

extraSignupFields: [{

fieldName: "username",

fieldLabel: "username",

inputType: 'text'

}]

});
추가 입력 필드
USER IN METHOD
• server/methods.js 에 사용자 정보 적용
• 로그인 여부 검사 위해 check 사용

meteor add check
• username은 Meteor.user().username
• profile_image는 gravatar를 사용하자

meteor add jparker:gravatar
USER IN METHOD
• client/main.js 에 Method.call 에 사용자 정보 제거

Template.main.events({

"submit": function(event, template) {

Meteor.call('addPosts', {

pageId: Session.get('pageId'),

message: template.find("#post").value

}, function(err, result) {

if (err) {

throw(err);

} else {

template.find('#post').value = '';

}

});

event.preventDefault();

}
사용자 정보는 서버에서
추가하고 pageId와
Message만 전송
USER IN METHOD
• server/methods.js 에 사용자 정보 적용

Meteor.methods({

"addPosts": function(obj) {

check(this.userId, String);

Posts.insert({

author: {

_id: this.userId,

name: Meteor.user().username,

profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address)+"?d=retro"

},

pageId: obj.pageId,

message: obj.message,

createdAt: new Date()

});

}

});
USER IN METHOD
• server/methods.js 에 사용자 정보 적용

Meteor.methods({

"addPosts": function(obj) {

check(this.userId, String);

Posts.insert({

author: {

_id: this.userId,

name: Meteor.user().username,

profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})

},

pageId: obj.pageId,

message: obj.message,

createdAt: new Date()

});

}

});
로그인 여부 체크
http://docs.meteor.com/#/full/check
USER IN METHOD
• server/methods.js 에 사용자 정보 적용

Meteor.methods({

"addPosts": function(obj) {

check(this.userId, String);

Posts.insert({

author: {

_id: this.userId,

name: Meteor.user().username,

profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})

},

pageId: obj.pageId,

message: obj.message,

createdAt: new Date()

});

}

});
사용자 ID
USER IN METHOD
• server/methods.js 에 사용자 정보 적용

Meteor.methods({

"addPosts": function(obj) {

check(this.userId, String);

Posts.insert({

author: {

_id: this.userId,

name: Meteor.user().username,

profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})

},

pageId: obj.pageId,

message: obj.message,

createdAt: new Date()

});

}

});
Accounts.ui.config에서 받은
사용자 이름
USER IN METHOD
• server/methods.js 에 사용자 정보 적용

Meteor.methods({

"addPosts": function(obj) {

check(this.userId, String);

Posts.insert({

author: {

_id: this.userId,

name: Meteor.user().username,

profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})

},

pageId: obj.pageId,

message: obj.message,

createdAt: new Date()

});

}

});
E-Mail 주소로 사용자
Image를 가져옴
USER IN METHOD
• server/methods.js 에 사용자 정보 적용

Meteor.methods({

"addPosts": function(obj) {

check(this.userId, String);

Posts.insert({

author: {

_id: this.userId,

name: Meteor.user().username,

profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})

},

pageId: obj.pageId,

message: obj.message,

createdAt: new Date()

});

}

});
(선택사항) 등록된 이미지가 없을 때

retro 아이콘을 임의로 생성

https://en.gravatar.com/site/implement/images/
생성일 추가
• posts.html

<template name="posts">

{{#each posts}}

<div class="media">

<div class="media-left">

<a href="#">

<img class="media-object" src="{{author.profile_image}}" alt="{{author.name}}">

</a>

</div>

<div class="media-body">

<h5 class="media-heading">{{author.name}}

- <i>{{createdAt}}</i>

</h5>

<div>

{{message}}

</div>

</div>

</div>

{{/each}}

</template>
가독성이 떨어진다.
좀 더 친근한 방법으로 표현할 수 없을까?
MOMENT
• 글별 상대시간 표시
• meteor add momentjs:moment
MOMENT
• Moment의Time From을 사용한다.

http://momentjs.com/docs/#/displaying/from/
• Template helper로 적용한다.

http://docs.meteor.com/#/full/template_helpers
생성일 추가
• client/posts.js

Template.posts.helpers({

…

"timeFrom": function(time) {

return moment().from(time);

}

});
• posts.html 수정

…

<h5 class="media-heading">{{author.name}}

- <i>{{timeFrom createdAt}}</i>

</h5>

…
REACTIVE
살아있는 실시간 값
FACEBOOK/TWITTER
• 별다른 행동을 하지 않았는데 가만히 보고 있으면...
• 알아서 시간이 변한다.
• Reactive Programming 을 활용해서 구현해보자.
REACTIVE PROGRAMMING
Don’t imperate, Just delcare
https://en.wikipedia.org/wiki/Reactive_programming
REACTIVETIME
• meteor add random 패키지 추가
• posts.js

Template.posts.onCreated(function() {

…

this.interval = Meteor.setInterval(function() {

Session.set('live', Random.id());

}, 1000);

});
• Session.set('live', ....) 하는 순간

Session.get('live')가 helper 이나 autorun 같은 곳 안쪽에 있으면 전부 재실행한다.

http://docs.meteor.com/#/full/reactivity

1초마다 live라는 키로 고유값을 생성
REACTIVETIME
• posts.js

Template.posts.helpers({

…

"timeFrom": function(time) {

Session.get('live');

return moment().from(time);

}

});
• 이때 live의 값이 변경이 없으면 해당 구문을 실행하지 않는다!
live를 변경하면 

timeFrom helper를 재실행
REACTIVE COMPUTATION
변경이 있을 때만 실행하여 효율적
LOGIN 여부
• Client

Meteor.userId()
• Server

this.userId()
• Template

{{#if currentUser}}
CURRENTUSER 적용
• main.html - 로그인 사용자만 글을 쓸 수 있게

{{#if currentUser}}

<form>

<div class="input-group">

........

</div>

</form>

{{/if}}
FOLLOW/UNFOLLOW
관심사 추적
FOLLOW/UNFOLLOW
• main.html

<h2>{{page}}'s Page

{{#if currentUser}}

{{#if isFollowing}}

<button id="unfollow" class="btn btn-inverse">unfollow</button>

{{else}}

<button id="follow" class="btn btn-primary">follow</button>

{{/if}}

{{/if}}

</h2>
접속여부 확인
Follwing 여부
FOLLOW/UNFOLLOW
• main.js helper 구현

사용자가 해당 토픽에 follow하고 있는지 검사

client에서 기본 접근 가능한 profile 객체를 사용

'isFollowing': function() {

var followings = Meteor.user().profile.followings;

return followings &&
followings[Session.get('pageId')];

}
FOLLOW/UNFOLLOW
• main.js event 구현. follow/unfollow

Template.main.events({

…..

"click #follow": function() {

Meteor.call('follow', Session.get('pageId'));

},

"click #unfollow": function() {

Meteor.call('unfollow', Session.get('pageId'));

}

});
FOLLOW/UNFOLLOW
• server/methods.js - Follow

"follow": function(pageId) {

check(this.userId, String);

var obj={};

obj["profile.followings."+pageId]={

createdAt: new Date()

};

Meteor.users.update(this.userId, {

$set: obj

});

},
• server/methods.js - Unfollow.

"unfollow": function(pageId) {

check(this.userId, String);

var obj={};

obj["profile.followings."+pageId]=
"";

Meteor.users.update(this.userId, {

$unset: obj

});

}
사용자확인
DASHBOARD
• 현재 사용자의 Follow한 Page를 모아 보는 기능
• Feeling Lucky - 무작위 포스트 이동 기능
DASHBOARD
• 홈 디렉토리 이동 시 Dashboard로
• head.html

<a class="navbar-brand" href="/">Sogon2x</a>
• / 일때 pageId를 리셋
• client/router.js

FlowRouter.route('/', {

action: function() {

Session.set('pageId');

}

});
DASHBOARD
• main.html 수정
• 페이지가 있으면 현재
페이지 (/page:pageId)

없으면 Dashboard로
분기
• {{> post}} helper에
pageId 인자 추가
• <template name="main">

<div class="container">

{{#if page}}

<h2>{{page}}'s Page

……

{{> posts
pageId=page}}

{{else}}

{{> dashboard}}

{{/if}}
DASHBOARD
• main.js 수정
• {{> post}} helper에 pageId 인자
전달
• Template.main.helpers({

'page': function() {

return Session.get('pageId');

},
• default 제거
• main.html / main.js 수정
• 페이지가 있으면 현재 페이지 (/page:pageId)

없으면 Dashboard로 분기
• <template name="main">

<div class="container">

{{#if page}}

<div>

<h2>{{page}}'s Page

……

{{> posts pageId=page}}

{{else}}

{{> dashboard}}

{{/if}}
DASHBOARD
• Template helper에서 받은 인자를 js에 적용

Session 에서 this.data.pageId로 변경
• posts.js 수정

Template.posts.onCreated(function() {

var pageId = this.data.pageId;

pageId && this.subscribe('getPage', pageId);

…

Template.posts.helpers({

"posts": function () {

return Posts.find({

pageId: Template.instance().data.pageId

}, {

…
this.data 로부터 상위
템플릿의 인자를 받는다.
Template.instance는
this.data와 같다.
Scope 이유로 다르게 씀.
DASHBOARD
• dashboard 화면 구성
• 필요한 데이터들을 Publish
• Reactive를 이용한 사용자 정보 변경 감지
DASHBOARD
• 운좋은 예감 - 무작위 Posts 추출

전체 데이터 갯수-count()이용-를 기준으로 랜덤만큼 skip하고 limit
을 이용해 1개만 값을 find한다.
• Meteor.publish('feelingLucky', function() {

return Posts.find({}, {

skip: Math.random()*Posts.find().count(),

limit: 1

});

});
DASHBOARD
• dashboard 생성 시 feelingLucky 를 구독(subscribe)
한다.
• client/dashboard.js 생성 후

Template.dashboard.onCreated(function() {

this.subscribe('feelingLucky');

});
DASHBOARD
• helper 정보 - luckyPage / pages
• dashboard.js

Template.dashboard.helpers({

'luckyPage': function() {

var post = Posts.findOne()

return post && post.pageId;

},

'pages': function() {

var result = [];

for (var i in Meteor.user().profile.followings) {

result.push({ pageId: i });

}

return result;

}

});
posts가 없을 때 오류 방지
DASHBOARD
• helper 정보 - luckyPage / pages
• dashboard.js

Template.dashboard.helpers({

'luckyPage': function() {

var post = Posts.findOne()

return post && post.pageId;

},

'pages': function() {

var result = [];

for (var i in Meteor.user().profile.followings) {

result.push({ pageId: i });

}

return result;

}

});
following 정보를 가져온다.
DASHBOARD
• helper 정보 - luckyPage / pages
• dashboard.js

Template.dashboard.helpers({

'luckyPage': function() {

var post = Posts.findOne()

return post && post.pageId;

},

'pages': function() {

var result = [];

for (var i in Meteor.user().profile.followings) {

result.push({ pageId: i });

}

return result;

}

});
pageId로 배열로 밀어넣는다.
DASHBOARD
• 화면 구성 - 사용자 여부에 따라 Feeling lucky와 최근 Posts를 나눠서 보여준다.
• dashboard.html

<template name="dashboard">

<div class="well">

<h2>Welcome to Sogon</h2>

<p>What do you want to talk about?</p>

<a href="/page/{{luckyPage}}" class="btn btn-primary">Feeling lucky</a>

</div>

{{#if currentUser}}

<h2>Recent Posts</h2>

{{#each pages}}

<h3><a href="/page/{{pageId}}">{{pageId}}</a></h3>

{{> posts pageId=pageId}}

{{/each}}

{{/if}}

</template>
운좋은 예감(랜덤링크)
사용자 정보가 “있으면”
following 중인 page들 목록
더 생각해 볼 것들
더 좋은 서비스를 위해
• MongoDB Operator의 사용. (ex: $addToSet, $pull 등)
• OAuth를 사용한 외부 서비스(페이스북/네이버/카카오) 로그인 연동
• 수정/삭제 기능
• 외부 공유와 검색엔진 최적화
• iOS/Android Hybrid Apps 제작
• Deploy …
참고 사이트
• https://github.com/MeteorKorea/meteor2015codelab

본 문서의 소스 코드 github 저장소
• http://meteorjs.rk 

Meteor Korea
• http://www.meetup.com/Meteor-Seoul

Meteor Seoul Meetup 모임
• http://kr.discovermeteor.com/

Discover Meteor 한글
• https://www.facebook.com/groups/meteorschool/

Facebook Meteor School

Mais conteúdo relacionado

Mais procurados

8주 dom & event basic 실습
8주  dom & event basic 실습8주  dom & event basic 실습
8주 dom & event basic 실습
지수 윤
 
9주 dom & event advanced 실습
9주  dom & event advanced 실습9주  dom & event advanced 실습
9주 dom & event advanced 실습
지수 윤
 

Mais procurados (19)

8주 dom & event basic 실습
8주  dom & event basic 실습8주  dom & event basic 실습
8주 dom & event basic 실습
 
9주 dom & event advanced 실습
9주  dom & event advanced 실습9주  dom & event advanced 실습
9주 dom & event advanced 실습
 
처음배우는 자바스크립트, 제이쿼리 #2
처음배우는 자바스크립트, 제이쿼리 #2처음배우는 자바스크립트, 제이쿼리 #2
처음배우는 자바스크립트, 제이쿼리 #2
 
Why javaScript?
Why javaScript?Why javaScript?
Why javaScript?
 
자바스크립트 프레임워크 살펴보기
자바스크립트 프레임워크 살펴보기자바스크립트 프레임워크 살펴보기
자바스크립트 프레임워크 살펴보기
 
Clean Front-End Development
Clean Front-End DevelopmentClean Front-End Development
Clean Front-End Development
 
Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.
 
Django로 쇼핑몰 만들자
Django로 쇼핑몰 만들자Django로 쇼핑몰 만들자
Django로 쇼핑몰 만들자
 
React를 이용하여 멀티플랫폼에서 개발하기
React를 이용하여 멀티플랫폼에서 개발하기React를 이용하여 멀티플랫폼에서 개발하기
React를 이용하여 멀티플랫폼에서 개발하기
 
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
 
[하코사세미나] 한 시간 만에 배우는 Jquery
[하코사세미나] 한 시간 만에 배우는 Jquery[하코사세미나] 한 시간 만에 배우는 Jquery
[하코사세미나] 한 시간 만에 배우는 Jquery
 
ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는
 
2017 Pycon KR - Django/AWS 를 이용한 쇼핑몰 서비스 구축
2017 Pycon KR - Django/AWS 를 이용한 쇼핑몰 서비스 구축2017 Pycon KR - Django/AWS 를 이용한 쇼핑몰 서비스 구축
2017 Pycon KR - Django/AWS 를 이용한 쇼핑몰 서비스 구축
 
스프링시큐리티와 소셜연습 이해를 위한 글
스프링시큐리티와 소셜연습 이해를 위한 글스프링시큐리티와 소셜연습 이해를 위한 글
스프링시큐리티와 소셜연습 이해를 위한 글
 
React 튜토리얼 2차시
React 튜토리얼 2차시React 튜토리얼 2차시
React 튜토리얼 2차시
 
[커빙 아키텍쳐] 커빙은 어떻게 소셜 컨텐츠를 모아올까요?
[커빙 아키텍쳐] 커빙은 어떻게 소셜 컨텐츠를 모아올까요?[커빙 아키텍쳐] 커빙은 어떻게 소셜 컨텐츠를 모아올까요?
[커빙 아키텍쳐] 커빙은 어떻게 소셜 컨텐츠를 모아올까요?
 
나의 jQuery 실력 향상기
나의 jQuery 실력 향상기나의 jQuery 실력 향상기
나의 jQuery 실력 향상기
 
Vuejs 시작하기
Vuejs 시작하기Vuejs 시작하기
Vuejs 시작하기
 
자바스크립트의 또다른 발전, Backbone.js
자바스크립트의 또다른 발전, Backbone.js자바스크립트의 또다른 발전, Backbone.js
자바스크립트의 또다른 발전, Backbone.js
 

Destaque

Destaque (15)

Meteor js - TechPeaks Developers Meeting
Meteor js - TechPeaks Developers MeetingMeteor js - TechPeaks Developers Meeting
Meteor js - TechPeaks Developers Meeting
 
Meteor + React
Meteor + ReactMeteor + React
Meteor + React
 
Bootstrap과 UI-Bootstrap
Bootstrap과 UI-BootstrapBootstrap과 UI-Bootstrap
Bootstrap과 UI-Bootstrap
 
Meteor + React
Meteor + ReactMeteor + React
Meteor + React
 
Webframeworks angular js 세미나
Webframeworks angular js 세미나Webframeworks angular js 세미나
Webframeworks angular js 세미나
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점 Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
 
Celltrion Healthcare 102 weeks with Remsima(non-HCP)
Celltrion Healthcare 102 weeks with Remsima(non-HCP)Celltrion Healthcare 102 weeks with Remsima(non-HCP)
Celltrion Healthcare 102 weeks with Remsima(non-HCP)
 
3 Growth Hacks: The Secrets to Driving Massive User Growth | Josh Elman, Grey...
3 Growth Hacks: The Secrets to Driving Massive User Growth | Josh Elman, Grey...3 Growth Hacks: The Secrets to Driving Massive User Growth | Josh Elman, Grey...
3 Growth Hacks: The Secrets to Driving Massive User Growth | Josh Elman, Grey...
 
One-day-codelab
One-day-codelabOne-day-codelab
One-day-codelab
 
Webframeworks.kr의 소개
Webframeworks.kr의 소개Webframeworks.kr의 소개
Webframeworks.kr의 소개
 
웹프레임워크를 이용하여 개발된 오픈소스 CMS프로젝트 샤인
웹프레임워크를 이용하여 개발된 오픈소스 CMS프로젝트 샤인웹프레임워크를 이용하여 개발된 오픈소스 CMS프로젝트 샤인
웹프레임워크를 이용하여 개발된 오픈소스 CMS프로젝트 샤인
 
Nodejs를 이용한 개발
Nodejs를 이용한 개발Nodejs를 이용한 개발
Nodejs를 이용한 개발
 
위플래닛 발표자료 Meteor_js
위플래닛 발표자료 Meteor_js위플래닛 발표자료 Meteor_js
위플래닛 발표자료 Meteor_js
 
Meteor를 통해서 개발하는 웹어플리케이션 서비스
Meteor를 통해서 개발하는 웹어플리케이션 서비스Meteor를 통해서 개발하는 웹어플리케이션 서비스
Meteor를 통해서 개발하는 웹어플리케이션 서비스
 
웹-프론트엔드 프레임워크를 고르기 위한 팁
웹-프론트엔드 프레임워크를 고르기 위한 팁웹-프론트엔드 프레임워크를 고르기 위한 팁
웹-프론트엔드 프레임워크를 고르기 위한 팁
 

Semelhante a Meteor2015 codelab

Html5&css 3장
Html5&css 3장Html5&css 3장
Html5&css 3장
홍준 김
 
컴포넌트 관점에서 개발하기
컴포넌트 관점에서 개발하기컴포넌트 관점에서 개발하기
컴포넌트 관점에서 개발하기
우영 주
 
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
yongwoo Jeon
 
웹성능최적화 20130405
웹성능최적화 20130405웹성능최적화 20130405
웹성능최적화 20130405
주형 전
 
더 나은 웹표준을 위한 Web Components
더 나은 웹표준을 위한 Web Components더 나은 웹표준을 위한 Web Components
더 나은 웹표준을 위한 Web Components
정호 전
 

Semelhante a Meteor2015 codelab (20)

Modern web application with meteor
Modern web application with meteorModern web application with meteor
Modern web application with meteor
 
현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)
현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)
현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)
 
Meteor React Tutorial 따라하기
Meteor React Tutorial 따라하기Meteor React Tutorial 따라하기
Meteor React Tutorial 따라하기
 
Html5&css 3장
Html5&css 3장Html5&css 3장
Html5&css 3장
 
컴포넌트 관점에서 개발하기
컴포넌트 관점에서 개발하기컴포넌트 관점에서 개발하기
컴포넌트 관점에서 개발하기
 
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
 
막하는 스터디 네 번째 만남 AngularJs (20151108)
막하는 스터디 네 번째 만남 AngularJs (20151108)막하는 스터디 네 번째 만남 AngularJs (20151108)
막하는 스터디 네 번째 만남 AngularJs (20151108)
 
웹성능최적화 20130405
웹성능최적화 20130405웹성능최적화 20130405
웹성능최적화 20130405
 
Django를 Django답게, Django로 뉴스 사이트 만들기
Django를 Django답게, Django로 뉴스 사이트 만들기Django를 Django답게, Django로 뉴스 사이트 만들기
Django를 Django답게, Django로 뉴스 사이트 만들기
 
알아봅시다, Polymer: Web Components & Web Animations
알아봅시다, Polymer: Web Components & Web Animations알아봅시다, Polymer: Web Components & Web Animations
알아봅시다, Polymer: Web Components & Web Animations
 
Coded ui가이드
Coded ui가이드Coded ui가이드
Coded ui가이드
 
Django - CRUD 기능 구현
Django - CRUD 기능 구현Django - CRUD 기능 구현
Django - CRUD 기능 구현
 
Html5
Html5 Html5
Html5
 
더 나은 웹표준을 위한 Web Components
더 나은 웹표준을 위한 Web Components더 나은 웹표준을 위한 Web Components
더 나은 웹표준을 위한 Web Components
 
Nexacro
NexacroNexacro
Nexacro
 
Hacosa j query 11th
Hacosa j query 11thHacosa j query 11th
Hacosa j query 11th
 
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
 
Opinion 프로젝트 발표 자료
Opinion 프로젝트 발표 자료Opinion 프로젝트 발표 자료
Opinion 프로젝트 발표 자료
 
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 TestOkjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
 
Word camp seoul-2012-track3-3
Word camp seoul-2012-track3-3Word camp seoul-2012-track3-3
Word camp seoul-2012-track3-3
 

Meteor2015 codelab

  • 1. MODERN WEB APPLICATION WITH METEOR 이재호 Appsoulute 대표 jhlee@appsoulute.com http://github.com/acidsound http://spectrumdig.blogspot.kr
  • 2. INSTALL METEOR Linux/OS X curl https://install.meteor.com/ | sh Windows https://install.meteor.com/windows
  • 3. 첫 METEOR APP • meteor로 시작하는 명령은 터미널이나 커맨드라인(시작 >실행>cmd)에서 입력합니 다. • meteor create sogon2x
  • 5. 구현 목표 관심사 기반 마이크로 블로깅 서비스 1. 화면생성 2. 포스트 입력 3. 이벤트 처리 4. 포스트 정렬 5. 사용자 계정 6. 구독/탈퇴 7. 대쉬보드
  • 7. TOOL • 어떤 걸로 코드를 만드실 건 가요? • ATOM (무료 추천!) • Sublime text (인기!) • Webstorm (유료 최고!)
  • 8. JAVASCRIPT 구조 CLIENT if (Meteor.isClient) { } SERVER if (Meteor.isServer) { Meteor.startup(function() { // code to run on server at startup }); } 사용자 
 브라우저에서 실행합니다. 서버에서 실행합니다.
  • 9. HTMLTEMPLATE (mobile first!) index.html <head> <title>sogon2x</title> <meta name="viewport" content="width=device- width, initial-scale=1"> </head> <body> {{> head}} {{> main}} </body> head.html <template name="head"> <h1>fixed header</h1> </template> main.html <template name="main"> <p>context</p> </template> emmet- meta:vp
  • 10. HEAD • https://atmospherejs.com/twbs/bootstrap • meteor add twbs:bootstrap • 적용 후 변화를 관찰 • navbar 사용
 http://bootstrapk.com/components/#navbar-brand- image
  • 11. HEAD - NAVBAR <template name="head"> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Sogon2X</a> </div> </div> </nav> </template>
  • 12. HEAD - NAVBAR <template name="main"> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Sogon2X</a> </div> </div> </nav> </template> 상단 네비게이션 바
  • 13. HEAD - NAVBAR <template name="main"> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Sogon2X</a> </div> </div> </nav> </template> 기본 색상
 navbar-inverse도 시도
  • 14. HEAD - NAVBAR <template name="main"> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Sogon2X</a> </div> </div> </nav> </template> 상단 고정 (optional)
  • 15. HEAD - NAVBAR <template name="main"> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Sogon2X</a> </div> </div> </nav> </template> 컨테이너 http://bootstrapk.com/css/#overview-container
  • 16. HEAD - NAVBAR <template name="main"> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Sogon2X</a> </div> </div> </nav> </template> Header 영역
  • 17. HEAD - NAVBAR <template name="main"> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="#">Sogon2X</a> </div> </div> </nav> </template> 로고 영역
  • 18. MAINTEMPLATE 버튼 애드온을 사용하여 입력 창을 만듭니다. http://bootstrapk.com/components/#input-groups- buttons
  • 19. MAINTEMPLATE <div class="container">
 <h2>Nobody's Page</h2>
 <form>
 <div class="input-group">
 <input type="text" id="post" class="form-control" placeholder="Tell me something..."/>
 <div class="input-group-btn">
 <button class="btn btn-primary">
 <i class="glyphicon glyphicon-pencil"></i>
 Post
 </button>
 </div>
 </div>
 </form>
 </div>
  • 20. MAINTEMPLATE <div class="container">
 <h2>Nobody's Page</h2>
 <form>
 <div class="input-group">
 <input type="text" id="post" class="form-control" placeholder="Tell me something..."/>
 <div class="input-group-btn">
 <button class="btn btn-primary">
 <i class="glyphicon glyphicon-pencil"></i>
 Post
 </button>
 </div>
 </div>
 </form>
 </div> 입력 그룹
 http://bootstrapk.com/components/#input-groups
  • 21. MAINTEMPLATE <div class="container">
 <h2>Nobody's Page</h2>
 <form>
 <div class="input-group">
 <input type="text" id="post" class="form-control" placeholder="Tell me something..."/>
 <div class="input-group-btn">
 <button class="btn btn-primary">
 <i class="glyphicon glyphicon-pencil"></i>
 Post
 </button>
 </div>
 </div>
 </form>
 </div> 폼 요소
  • 22. MAINTEMPLATE <div class="container">
 <h2>Nobody's Page</h2>
 <form>
 <div class="input-group">
 <input type="text" id="post" class="form-control" placeholder="Tell me something..."/>
 <div class="input-group-btn">
 <button class="btn btn-primary">
 <i class="glyphicon glyphicon-pencil"></i>
 Post
 </button>
 </div>
 </div>
 </form>
 </div> 버튼 애드온 http://bootstrapk.com/components/#input-groups-buttons
  • 23. MAINTEMPLATE <div class="container">
 <h2>Nobody's Page</h2>
 <form>
 <div class="input-group">
 <input type="text" id="post" class="form-control" placeholder="Tell me something..."/>
 <div class="input-group-btn">
 <button class="btn btn-primary">
 <i class="glyphicon glyphicon-pencil"></i>
 Post
 </button>
 </div>
 </div>
 </form>
 </div> 버튼 옵션 http://bootstrapk.com/css/#buttons-options
  • 24. MAINTEMPLATE <div class="container">
 <h2>Nobody's Page</h2>
 <form>
 <div class="input-group">
 <input type="text" id="post" class="form-control" placeholder="Tell me something..."/>
 <div class="input-group-btn">
 <button class="btn btn-primary">
 <i class="glyphicon glyphicon-pencil"></i>
 Post
 </button>
 </div>
 </div>
 </form>
 </div> 아이콘 http://bootstrapk.com/components/#glyphicons
  • 25. POSTTEMPLATE • Main 아래 Post들의 목록을 열거하는 화면구성 • media를 사용하여 UI를 먼저 만든다. • http://bootstrapk.com/components/#media-default
  • 26. POSTTEMPLATE • main template 아래에 
 {{> posts}} 를 추가하여 posts라는 템플릿을 붙여주 도록한다. <template name="main">
 <div class="container"> …. {{> posts}} </div> </template>
  • 27. POSTTEMPLATE • Main 아래 Post들의 목록을 열거하는 화면구성 • media를 사용하여 UI를 먼저 만든다. • http://bootstrapk.com/components/#media-default
  • 28. POSTTEMPLATE<template name="posts">
 <div class="media">
 <div class="media-left">
 <a href="#">
 <img class="media-object" src="http://lorempixel.com/64/64/cats/" alt="nobody">
 </a>
 </div>
 <div class="media-body">
 <h4 class="media-heading">Master</h4>
 집사야 내 밥은 어디있냐?
 </div>
 </div>
 <div class="media">
 <div class="media-left">
 <a href="#">
 <img class="media-object" src="http://lorempixel.com/64/64/people/" alt="nobody">
 </a>
 </div>
 <div class="media-body">
 <h4 class="media-heading">Slave4U</h4>
 배고파서 내가 먹었다.
 </div>
 </div>
 </template>
  • 29. POSTTEMPLATE<template name="posts">
 <div class="media">
 <div class="media-left">
 <a href="#">
 <img class="media-object" src="http://lorempixel.com/64/64/cats/" alt="nobody">
 </a>
 </div>
 <div class="media-body">
 <h4 class="media-heading">Master</h4>
 집사야 내 밥은 어디있냐?
 </div>
 </div>
 <div class="media">
 <div class="media-left">
 <a href="#">
 <img class="media-object" src="http://lorempixel.com/64/64/people/" alt="nobody">
 </a>
 </div>
 <div class="media-body">
 <h4 class="media-heading">Slave4U</h4>
 배고파서 내가 먹었다.
 </div>
 </div>
 </template> 반복구간
  • 32. 일단 이사 먼저! • client 폴더를 만듭니다. • 지금까지 만든 모든 html파 일들을 client 아래로 이동합 니다. • 같은 곳에 posts.js를 만들어 줍니다.
  • 33. POSTTEMPLATE 가짜로 자료를 만듭니다. posts.js 안에 반복 구간에 들어갈 값들을 JSON 형태 로 만들어봅시다.
  • 34. POSTTEMPLATE Template.posts.helpers({
 "posts": function() {
 return [
 {
 author: {
 name: "Master",
 profile_image: "http://lorempixel.com/64/64/cats/"
 },
 message: "집사야 내 밥은 어딨냐?"
 },
 {
 author: {
 name: "Slave4U",
 profile_image: "http://lorempixel.com/64/64/people/"
 },
 message: "배고파서 내가 먹었다."
 }
 ]
 }
 });
  • 35. POSTTEMPLATE • posts.html에 반복 구간을 정하고 값을 받을 helper 들로 교체합니다.
  • 36. POSTTEMPLATE <template name="posts">
 <div class="media">
 <div class="media-left">
 <a href="#">
 <img class="media-object" src="http://lorempixel.com/64/64/ cats/" alt="nobody">
 </a>
 </div>
 <div class="media-body">
 <h4 class="media-heading">Master</h4>
 집사야 내 밥은 어디있냐?
 </div>
 </div>
 </template>
  • 37. POSTTEMPLATE<template name="posts">
 {{#each posts}}
 <div class="media">
 <div class="media-left">
 <a href="#">
 <img class="media-object" src="http://lorempixel.com/64/64/cats/" alt="nobody">
 </a>
 </div>
 <div class="media-body">
 <h4 class="media-heading">Master</h4>
 집사야 내 밥은 어디있냐?
 </div>
 </div>
 {{/each}}
 </template>
  • 38. POSTTEMPLATE<template name="posts">
 {{#each posts}}
 <div class="media">
 <div class="media-left">
 <a href="#">
 <img class="media-object" src="{{author.profile_image}}" alt="{{author.name}}">
 </a>
 </div>
 <div class="media-body">
 <h4 class=“media-heading”>{{author.name}}</h4>
 {{message}}
 </div>
 </div>
 {{/each}}
 </template>
  • 40. CONNECT DB • lib/collection.js 에 추가
 Posts = new Mongo.Collection('posts'); • client/server 양쪽에 적용 • 기존 posts.js 수정
 Template.posts.helpers({
 "posts": function() {
 return Posts.find();
 }
 });
  • 41. CONNECT DB • Browser Console에서 테스트 • Posts.insert({
 author: {
 name: "Master",
 profile_image: "http://lorempixel.com/64/64/cats/"
 },
 message: "집사야 내 밥은 어딨냐?"
 }); • Posts.find().fetch(); • 화면과 결과값을 확인
  • 43. REMOVE INSECURE • meteor remove insecure • insert failed:Access denied • 사용자가 임의로 데이터 조작을 할 수 없음
  • 44. METHODS • server/methods.js - 서버에서만 insert
 Meteor.methods({
 "addPosts": function(obj) {
 Posts.insert({
 author: {
 name: obj.name,
 profile_image: obj.profile_image
 },
 message: obj.message
 });
 }
 }); 44
  • 45. CLIENT CALL • Method.call 사용. 콘솔에서 테스트.
 Meteor.call("addPosts", {
 name: "Slave4U",
 profile_image: "http://lorempixel.com/64/64/ people/",
 message: "배고파서 내가 다 먹었다."
 }); 45
  • 46. EVENT HANDLING • Template.main.events({
 "submit": function(event, template) {
 Meteor.call("addPosts", {
 name: "Slave4U",
 profile_image: "http://lorempixel.com/64/64/people/",
 message : template.find('#post').value
 }, function(err, result) {
 if (err) {
 throw(error);
 } else {
 console.log(result);
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 46 사용자 로그인과 연동 필요
  • 47. EVENT HANDLING • Template.main.events({
 "submit": function(event, template) {
 Meteor.call("addPosts", {
 name: "Slave4U",
 profile_image: "http://lorempixel.com/64/64/people/",
 message : template.find('#post').value
 }, function(err) {
 if (err) {
 throw(error);
 } else {
 console.log(result);
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 47 템플릿 안에서 post라는 id를 가진 객체를 검색. 그 값을 가져온다.
  • 48. EVENT HANDLING • Template.main.events({
 "submit": function(event, template) {
 Meteor.call("addPosts", {
 name: "Slave4U",
 profile_image: "http://lorempixel.com/64/64/people/",
 message : template.find('#post').value
 }, function(err) {
 if (err) {
 throw(error);
 } else {
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 48 method call 후 오류처리
  • 49. EVENT HANDLING • Template.main.events({
 "submit": function(event, template) {
 Meteor.call("addPosts", {
 name: "Slave4U",
 profile_image: "http://lorempixel.com/64/64/people/",
 message : template.find('#post').value
 }, function(err) {
 if (err) {
 throw(error);
 } else {
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 49 처리 성공 후 입력창 내용 삭제
  • 50. EVENT HANDLING • Template.main.events({
 "submit": function(event, template) {
 Meteor.call("addPosts", {
 name: "Slave4U",
 profile_image: "http://lorempixel.com/64/64/people/",
 message : template.find('#post').value
 }, function(err) {
 if (err) {
 throw(error);
 } else {
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 50 기존 submit 이벤트를 금지 페이지 이동이 안되도록 제한
  • 51. RESET DATABASE • 서버 정지 • meteor reset • 재기동
  • 52. ADDPOSTS • server/methods.js - 서버에서만 insert
 Meteor.methods({
 "addPosts": function(obj) {
 Posts.insert({
 author: {
 name: obj.name,
 profile_image: obj.profile_image
 },
 message: obj.message,
 createdAt: new Date()
 });
 }
 }); 52 반드시 서버 시간!
  • 53. SORT BYTIME DESC • 시간 역순 정렬. Server 시간 기준 • http://docs.meteor.com/#/full/sortspecifiers • Posts.find({}, {
 sort: {
 createdAt: -1
 }
 });
  • 54. POSTS HELPER • posts.js
 Template.posts.helpers({
 "posts": function() {
 return Posts.find({}, {
 sort: {
 createdAt: -1
 }
 });
 }
 }); 정렬순서
 -1 : 내림차순 1 : 오름차순
  • 56. SESSION • Session의 장점
 전역으로 사용할 수 있다.
 브라우저 콘솔에서 사용이 자유롭다.
 서버 재시작 이후에도 값을 유지한다. • Session의 단점
 전역으로 밖에 사용할 수 없다.
 Deprecated 예정
  • 57. SESSION 사용법 • Session의 읽기 
 Session.get('pageId'); • Session의 쓰기
 Session.set('pageId', 'catLover');
  • 58. SESSION 적용 • main.js
 Template.main.helpers({
 'page': function() {
 return Session.get('pageId');
 }
 }); • main.html
 <template name="main">
 <div class="container">
 <h2>{{page}}'s Page</h2>
 …
  • 59. SESSION.SET • 브라우저 콘솔에서
 Session.set('pageId', 'catLover') • 바로 화면이 갱신되는 것을 관찰 • 어째서 이렇게 될까?
 Reactive Programming!
 http://docs.meteor.com/#/full/reactivity
  • 60. PUBLISH/SUBSCRIBE • 보고싶은 것만 보고 싶어요. • meteor remove autopublish
  • 61. AUTOPUBLISH? • insecure 처럼 기본 설치 Meteor package • Collection의 모든 내용을 서버로부터 가져온다. • 하지만 우리는 page별로 따로따로 보고 싶다.
  • 64. REMOVE AUTOPUBLISH • meteor remove autopublish • 어? 아무것도 안나와요?????
  • 65. DON’T PANIC • 원래대로 돌려놓아 봅시다. • server/publish.js 추가
 Meteor.publish('getPage', function() {
 return Posts.find();
 }); • 브라우저 콘솔에서 확인해보자
 Meteor.subscribe('getPage');
  • 66. MANUAL SUBSCRIPTION • main.js에 subscribe 추가
 Template.main.onCreated(function() {
 this.subscribe('getPage');
 }); • 원래대로 돌아왔다!
  • 67. PUB/SUB BASIC • Server에서 publish 한 데이터를...
 Meteor.publish('publishName', function() {
 return YourCollection.find();
 }); • client에서 subscribe 에서 가져온다.
 Template.yourTemplate.onCreated(function() {
 this.subscribe('publishName');
 }); • 간단하죠?
  • 68. PUBLISH WITH PAGEID • 조건을 주고 필요한 것들만 가져옵니다.
 (http://docs.meteor.com/#/full/selectors) • server/publish.js 수정
 Meteor.publish('getPage', function(pageId) {
 return Posts.find({pageId: pageId});
 });
  • 69. SUBSCRIBE WITH PAGEID • client/main.js 수정
 Template.main.helpers({
 'page': function() {
 return Session.get('pageId') || 'popular';
 }
 }); • client/posts.js 수정
 Template.posts.onCreated(function() {
 this.subscribe('getPage', Session.get('pageId'));
 }); pageId가 없으면
 popular를 기본으로 pageId로 가입
  • 70. CALL WITH PAGEID • client/main.js 수정
 Template.main.events({
 "submit": function(event, template) {
 Meteor.call("addPosts", {
 name: "Slave4U",
 profile_image: "http://lorempixel.com/64/64/people/",
 pageId: Session.get('pageId'),
 message : template.find('#post').value
 }, function(err) {
 …
  • 71. METHOD WITH PAGEID • server/methods.js 수정
 Meteor.methods({
 "addPosts": function(obj) {
 Posts.insert({
 author: {
 name: obj.name,
 profile_image: obj.profile_image
 },
 pageId: obj.pageId,
 message: obj.message,
 createdAt: new Date()
 });
 }
 })
  • 73. KEYWORD별 POSTS • 같은 관심사를 가진 사람들끼리 이야기 할 수 있도록 POSTS를 분리 • 채널이나 대화방 같은 느낌 • Page라는 이름으로 분리 • URL로 구분
 /page/keyword
  • 74. ROUTING • Routing용 package 설치 • meteor add kadira:flow-router
  • 75. WARNING! • Flow-router는 third-party package입니다.
 작성자가 꼭 업데이트를 보증하지 않습니다. • 어떤 Router를 사용할지는 선택할 수 있습니다. • Single Page Application에서 Routing(URL 경로)가 꼭 필수이진 않습니다.
  • 76. ROUTER 만들기 • https://kadira.io/academy/meteor-routing-guide/content/ introduction-to-flow-router • client/router.js 생성 (원래 이렇게 쓰는 건 아니에요!)
 FlowRouter.route('/page/:pageId', {
 name: 'main',
 action: function(params) {
 Session.set('pageId', params.pageId);
 }
 }); 인자를 받아서 Session에 기록한다.
  • 78. ACCOUNTS PACKAGE • meteor add accounts-password • http://docs.meteor.com/#/full/accounts_api • Meteor.user() - 현재 접속중인 사용자 • Meteor.userId() - 접속 중인 사용자 ID • Meteor.loginWithPassword(user, password, [callback]) 
 로그인하기, 성공 시 callback function 실행 • Meteor.logout() - 로그아웃 • Accounts.createUser(option, [callback]) - 사용자 생성
  • 79. ACCOUNTS PACKAGES • meteor add accounts- password
 
 E-mail/password 인증 • meteor add ian:accounts-ui- bootstrap-3
 
 bootstrap3용 accounts UI • Template에 {{> loginButtons}}
  • 80. LOGINBUTTONS <template name="head">
 <nav class="navbar navbar-default navbar-static-top">
 <div class="container">
 <div class="navbar-header">
 <a class="navbar-brand" href="#">Sogon2x</a>
 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
 data-target=".navbar-collapse">
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 </button>
 </div>
 <div class="navbar-collapse collapse">
 <ul class="nav navbar-nav navbar-right">{{> loginButtons}}</ul>
 </div>
 </div>
 </nav>
 </template> https://github.com/ianmartorell/meteor- accounts-ui-bootstrap-3/#how-to-use
  • 81. LOGINBUTTONS <template name="head">
 <nav class="navbar navbar-default navbar-static-top">
 <div class="container">
 <div class="navbar-header">
 <a class="navbar-brand" href="#">Sogon2x</a>
 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
 data-target=".navbar-collapse">
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 </button>
 </div>
 <div class="navbar-collapse collapse">
 <ul class="nav navbar-nav navbar-right">{{> loginButtons}}</ul>
 </div>
 </div>
 </nav>
 </template> 모바일에서 접히는 영역
  • 82. LOGINBUTTONS <template name="head">
 <nav class="navbar navbar-default navbar-static-top">
 <div class="container">
 <div class="navbar-header">
 <a class="navbar-brand" href="#">Sogon2x</a>
 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
 data-target=".navbar-collapse">
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 <span class="icon-bar"></span>
 </button>
 </div>
 <div class="navbar-collapse collapse">
 <ul class="nav navbar-nav navbar-right">{{> loginButtons}}</ul>
 </div>
 </div>
 </nav>
 </template> loginButtons 삽입 (MAGIC!!)
  • 83. USERNAME • 사용자명 추가 • https://github.com/ianmartorell/meteor-accounts-ui-bootstrap-3/#custom-signup- options • client/config.js
 Accounts.ui.config({
 extraSignupFields: [{
 fieldName: "username",
 fieldLabel: "username",
 inputType: 'text'
 }]
 }); 추가 입력 필드
  • 84. USER IN METHOD • server/methods.js 에 사용자 정보 적용 • 로그인 여부 검사 위해 check 사용
 meteor add check • username은 Meteor.user().username • profile_image는 gravatar를 사용하자
 meteor add jparker:gravatar
  • 85. USER IN METHOD • client/main.js 에 Method.call 에 사용자 정보 제거
 Template.main.events({
 "submit": function(event, template) {
 Meteor.call('addPosts', {
 pageId: Session.get('pageId'),
 message: template.find("#post").value
 }, function(err, result) {
 if (err) {
 throw(err);
 } else {
 template.find('#post').value = '';
 }
 });
 event.preventDefault();
 } 사용자 정보는 서버에서 추가하고 pageId와 Message만 전송
  • 86. USER IN METHOD • server/methods.js 에 사용자 정보 적용
 Meteor.methods({
 "addPosts": function(obj) {
 check(this.userId, String);
 Posts.insert({
 author: {
 _id: this.userId,
 name: Meteor.user().username,
 profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address)+"?d=retro"
 },
 pageId: obj.pageId,
 message: obj.message,
 createdAt: new Date()
 });
 }
 });
  • 87. USER IN METHOD • server/methods.js 에 사용자 정보 적용
 Meteor.methods({
 "addPosts": function(obj) {
 check(this.userId, String);
 Posts.insert({
 author: {
 _id: this.userId,
 name: Meteor.user().username,
 profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})
 },
 pageId: obj.pageId,
 message: obj.message,
 createdAt: new Date()
 });
 }
 }); 로그인 여부 체크 http://docs.meteor.com/#/full/check
  • 88. USER IN METHOD • server/methods.js 에 사용자 정보 적용
 Meteor.methods({
 "addPosts": function(obj) {
 check(this.userId, String);
 Posts.insert({
 author: {
 _id: this.userId,
 name: Meteor.user().username,
 profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})
 },
 pageId: obj.pageId,
 message: obj.message,
 createdAt: new Date()
 });
 }
 }); 사용자 ID
  • 89. USER IN METHOD • server/methods.js 에 사용자 정보 적용
 Meteor.methods({
 "addPosts": function(obj) {
 check(this.userId, String);
 Posts.insert({
 author: {
 _id: this.userId,
 name: Meteor.user().username,
 profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})
 },
 pageId: obj.pageId,
 message: obj.message,
 createdAt: new Date()
 });
 }
 }); Accounts.ui.config에서 받은 사용자 이름
  • 90. USER IN METHOD • server/methods.js 에 사용자 정보 적용
 Meteor.methods({
 "addPosts": function(obj) {
 check(this.userId, String);
 Posts.insert({
 author: {
 _id: this.userId,
 name: Meteor.user().username,
 profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})
 },
 pageId: obj.pageId,
 message: obj.message,
 createdAt: new Date()
 });
 }
 }); E-Mail 주소로 사용자 Image를 가져옴
  • 91. USER IN METHOD • server/methods.js 에 사용자 정보 적용
 Meteor.methods({
 "addPosts": function(obj) {
 check(this.userId, String);
 Posts.insert({
 author: {
 _id: this.userId,
 name: Meteor.user().username,
 profile_image: Gravatar.imageUrl(Meteor.user().emails[0].address, {d: "retro"})
 },
 pageId: obj.pageId,
 message: obj.message,
 createdAt: new Date()
 });
 }
 }); (선택사항) 등록된 이미지가 없을 때
 retro 아이콘을 임의로 생성
 https://en.gravatar.com/site/implement/images/
  • 92. 생성일 추가 • posts.html
 <template name="posts">
 {{#each posts}}
 <div class="media">
 <div class="media-left">
 <a href="#">
 <img class="media-object" src="{{author.profile_image}}" alt="{{author.name}}">
 </a>
 </div>
 <div class="media-body">
 <h5 class="media-heading">{{author.name}}
 - <i>{{createdAt}}</i>
 </h5>
 <div>
 {{message}}
 </div>
 </div>
 </div>
 {{/each}}
 </template>
  • 93. 가독성이 떨어진다. 좀 더 친근한 방법으로 표현할 수 없을까?
  • 94. MOMENT • 글별 상대시간 표시 • meteor add momentjs:moment
  • 95. MOMENT • Moment의Time From을 사용한다.
 http://momentjs.com/docs/#/displaying/from/ • Template helper로 적용한다.
 http://docs.meteor.com/#/full/template_helpers
  • 96. 생성일 추가 • client/posts.js
 Template.posts.helpers({
 …
 "timeFrom": function(time) {
 return moment().from(time);
 }
 }); • posts.html 수정
 …
 <h5 class="media-heading">{{author.name}}
 - <i>{{timeFrom createdAt}}</i>
 </h5>
 …
  • 98. FACEBOOK/TWITTER • 별다른 행동을 하지 않았는데 가만히 보고 있으면... • 알아서 시간이 변한다. • Reactive Programming 을 활용해서 구현해보자.
  • 99. REACTIVE PROGRAMMING Don’t imperate, Just delcare https://en.wikipedia.org/wiki/Reactive_programming
  • 100. REACTIVETIME • meteor add random 패키지 추가 • posts.js
 Template.posts.onCreated(function() {
 …
 this.interval = Meteor.setInterval(function() {
 Session.set('live', Random.id());
 }, 1000);
 }); • Session.set('live', ....) 하는 순간
 Session.get('live')가 helper 이나 autorun 같은 곳 안쪽에 있으면 전부 재실행한다.
 http://docs.meteor.com/#/full/reactivity
 1초마다 live라는 키로 고유값을 생성
  • 101. REACTIVETIME • posts.js
 Template.posts.helpers({
 …
 "timeFrom": function(time) {
 Session.get('live');
 return moment().from(time);
 }
 }); • 이때 live의 값이 변경이 없으면 해당 구문을 실행하지 않는다! live를 변경하면 
 timeFrom helper를 재실행
  • 102. REACTIVE COMPUTATION 변경이 있을 때만 실행하여 효율적
  • 103. LOGIN 여부 • Client
 Meteor.userId() • Server
 this.userId() • Template
 {{#if currentUser}}
  • 104. CURRENTUSER 적용 • main.html - 로그인 사용자만 글을 쓸 수 있게
 {{#if currentUser}}
 <form>
 <div class="input-group">
 ........
 </div>
 </form>
 {{/if}}
  • 106. FOLLOW/UNFOLLOW • main.html
 <h2>{{page}}'s Page
 {{#if currentUser}}
 {{#if isFollowing}}
 <button id="unfollow" class="btn btn-inverse">unfollow</button>
 {{else}}
 <button id="follow" class="btn btn-primary">follow</button>
 {{/if}}
 {{/if}}
 </h2> 접속여부 확인 Follwing 여부
  • 107. FOLLOW/UNFOLLOW • main.js helper 구현
 사용자가 해당 토픽에 follow하고 있는지 검사
 client에서 기본 접근 가능한 profile 객체를 사용
 'isFollowing': function() {
 var followings = Meteor.user().profile.followings;
 return followings && followings[Session.get('pageId')];
 }
  • 108. FOLLOW/UNFOLLOW • main.js event 구현. follow/unfollow
 Template.main.events({
 …..
 "click #follow": function() {
 Meteor.call('follow', Session.get('pageId'));
 },
 "click #unfollow": function() {
 Meteor.call('unfollow', Session.get('pageId'));
 }
 });
  • 109. FOLLOW/UNFOLLOW • server/methods.js - Follow
 "follow": function(pageId) {
 check(this.userId, String);
 var obj={};
 obj["profile.followings."+pageId]={
 createdAt: new Date()
 };
 Meteor.users.update(this.userId, {
 $set: obj
 });
 }, • server/methods.js - Unfollow.
 "unfollow": function(pageId) {
 check(this.userId, String);
 var obj={};
 obj["profile.followings."+pageId]= "";
 Meteor.users.update(this.userId, {
 $unset: obj
 });
 } 사용자확인
  • 110. DASHBOARD • 현재 사용자의 Follow한 Page를 모아 보는 기능 • Feeling Lucky - 무작위 포스트 이동 기능
  • 111. DASHBOARD • 홈 디렉토리 이동 시 Dashboard로 • head.html
 <a class="navbar-brand" href="/">Sogon2x</a> • / 일때 pageId를 리셋 • client/router.js
 FlowRouter.route('/', {
 action: function() {
 Session.set('pageId');
 }
 });
  • 112. DASHBOARD • main.html 수정 • 페이지가 있으면 현재 페이지 (/page:pageId)
 없으면 Dashboard로 분기 • {{> post}} helper에 pageId 인자 추가 • <template name="main">
 <div class="container">
 {{#if page}}
 <h2>{{page}}'s Page
 ……
 {{> posts pageId=page}}
 {{else}}
 {{> dashboard}}
 {{/if}}
  • 113. DASHBOARD • main.js 수정 • {{> post}} helper에 pageId 인자 전달 • Template.main.helpers({
 'page': function() {
 return Session.get('pageId');
 }, • default 제거 • main.html / main.js 수정 • 페이지가 있으면 현재 페이지 (/page:pageId)
 없으면 Dashboard로 분기 • <template name="main">
 <div class="container">
 {{#if page}}
 <div>
 <h2>{{page}}'s Page
 ……
 {{> posts pageId=page}}
 {{else}}
 {{> dashboard}}
 {{/if}}
  • 114. DASHBOARD • Template helper에서 받은 인자를 js에 적용
 Session 에서 this.data.pageId로 변경 • posts.js 수정
 Template.posts.onCreated(function() {
 var pageId = this.data.pageId;
 pageId && this.subscribe('getPage', pageId);
 …
 Template.posts.helpers({
 "posts": function () {
 return Posts.find({
 pageId: Template.instance().data.pageId
 }, {
 … this.data 로부터 상위 템플릿의 인자를 받는다. Template.instance는 this.data와 같다. Scope 이유로 다르게 씀.
  • 115. DASHBOARD • dashboard 화면 구성 • 필요한 데이터들을 Publish • Reactive를 이용한 사용자 정보 변경 감지
  • 116. DASHBOARD • 운좋은 예감 - 무작위 Posts 추출
 전체 데이터 갯수-count()이용-를 기준으로 랜덤만큼 skip하고 limit 을 이용해 1개만 값을 find한다. • Meteor.publish('feelingLucky', function() {
 return Posts.find({}, {
 skip: Math.random()*Posts.find().count(),
 limit: 1
 });
 });
  • 117. DASHBOARD • dashboard 생성 시 feelingLucky 를 구독(subscribe) 한다. • client/dashboard.js 생성 후
 Template.dashboard.onCreated(function() {
 this.subscribe('feelingLucky');
 });
  • 118. DASHBOARD • helper 정보 - luckyPage / pages • dashboard.js
 Template.dashboard.helpers({
 'luckyPage': function() {
 var post = Posts.findOne()
 return post && post.pageId;
 },
 'pages': function() {
 var result = [];
 for (var i in Meteor.user().profile.followings) {
 result.push({ pageId: i });
 }
 return result;
 }
 }); posts가 없을 때 오류 방지
  • 119. DASHBOARD • helper 정보 - luckyPage / pages • dashboard.js
 Template.dashboard.helpers({
 'luckyPage': function() {
 var post = Posts.findOne()
 return post && post.pageId;
 },
 'pages': function() {
 var result = [];
 for (var i in Meteor.user().profile.followings) {
 result.push({ pageId: i });
 }
 return result;
 }
 }); following 정보를 가져온다.
  • 120. DASHBOARD • helper 정보 - luckyPage / pages • dashboard.js
 Template.dashboard.helpers({
 'luckyPage': function() {
 var post = Posts.findOne()
 return post && post.pageId;
 },
 'pages': function() {
 var result = [];
 for (var i in Meteor.user().profile.followings) {
 result.push({ pageId: i });
 }
 return result;
 }
 }); pageId로 배열로 밀어넣는다.
  • 121. DASHBOARD • 화면 구성 - 사용자 여부에 따라 Feeling lucky와 최근 Posts를 나눠서 보여준다. • dashboard.html
 <template name="dashboard">
 <div class="well">
 <h2>Welcome to Sogon</h2>
 <p>What do you want to talk about?</p>
 <a href="/page/{{luckyPage}}" class="btn btn-primary">Feeling lucky</a>
 </div>
 {{#if currentUser}}
 <h2>Recent Posts</h2>
 {{#each pages}}
 <h3><a href="/page/{{pageId}}">{{pageId}}</a></h3>
 {{> posts pageId=pageId}}
 {{/each}}
 {{/if}}
 </template> 운좋은 예감(랜덤링크) 사용자 정보가 “있으면” following 중인 page들 목록
  • 122. 더 생각해 볼 것들
  • 123. 더 좋은 서비스를 위해 • MongoDB Operator의 사용. (ex: $addToSet, $pull 등) • OAuth를 사용한 외부 서비스(페이스북/네이버/카카오) 로그인 연동 • 수정/삭제 기능 • 외부 공유와 검색엔진 최적화 • iOS/Android Hybrid Apps 제작 • Deploy …
  • 124. 참고 사이트 • https://github.com/MeteorKorea/meteor2015codelab
 본 문서의 소스 코드 github 저장소 • http://meteorjs.rk 
 Meteor Korea • http://www.meetup.com/Meteor-Seoul
 Meteor Seoul Meetup 모임 • http://kr.discovermeteor.com/
 Discover Meteor 한글 • https://www.facebook.com/groups/meteorschool/
 Facebook Meteor School