SlideShare a Scribd company logo
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

cd sogon2x
APP 실행하기
• meteor run 혹은 

meteor
• App running at …메시지
가 나오면 브라우저에서
http://localhost:3000 을
띄운다.
구현 목표
관심사 기반 마이크로 블로깅 서비스
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
});
}
사용자 

브라우저에서
실행합니다.
서버에서
실행합니다.
HTML TEMPLATE
(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> 로고 영역
MAIN TEMPLATE
버튼 애드온을 사용하여 입력 창을 만듭니다.
http://bootstrapk.com/components/#input-
groups-buttons
MAIN TEMPLATE
<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>
MAIN TEMPLATE
<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
MAIN TEMPLATE
<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> 폼 요소
MAIN TEMPLATE
<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
MAIN TEMPLATE
<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
MAIN TEMPLATE
<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
POSTS TEMPLATE
•Main 아래 Post들의 목록을 열거하는 화면구성
•media를 사용하여 UI를 먼저 만든다.
•http://bootstrapk.com/components/
#media-default
POSTS TEMPLATE
• main template 아래에 

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

<div class="container">
….
{{> posts}}
</div>
</template>
POSTS TEMPLATE
•Main 아래 Post들의 목록을 열거하는 화면구성
•media를 사용하여 UI를 먼저 만든다.
•http://bootstrapk.com/components/
#media-default
•client/posts.html 을 생성한다.
POSTS TEMPLATE<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>
POSTS TEMPLATE<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>
반복구간
POSTS TEMPLATE
{{#each posts}}
….
{{/each}}
• 반복 구간 처리
이제 코딩을 합시다.
Let’s Do Some Coding!
POST TEMPLATE
가짜로 자료를 만듭니다.
posts.js 안에 반복 구간에 들어갈 값들을 JSON
형태로 만들어봅시다.
POST TEMPLATETemplate.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: "배고파서 내가 먹었다."

}

]

}

});
POST TEMPLATE
• posts.html에 반복 구간을 정하고 값을 받을
helper들로 교체합니다.
POST TEMPLATE
<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>
POST TEMPLATE
<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>
POST TEMPLATE
<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

});

}

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

Meteor.call("addPosts", {

name: "Slave4U",

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

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

});
44
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(err);

} else {

console.log(result);

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

}

});

event.preventDefault();

}

});
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) {

if (err) {

throw(err);

} else {

console.log(result);

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

}

});

event.preventDefault();

}

});
46
템플릿 안에서 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(err);

} else {

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

}

});

event.preventDefault();

}

});
47
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(err);

} else {

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

}

});

event.preventDefault();

}

});
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(err);

} else {

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

}

});

event.preventDefault();

}

});
49
기존 submit 이벤트를 금지
페이지 이동이 안되도록 제한
RESET DATABASE
•서버 정지
•meteor reset
•재기동

meteor
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()

});

}

});
51
반드시 서버 시간!
SORT BY TIME 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
REACTIVE TIME
•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라는 키로 고유값을 생성
REACTIVE TIME
•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>
접속여부 확인
Following 여부
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.kr 

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

More Related Content

What's hot

Spring Boot 2
Spring Boot 2Spring Boot 2
Spring Boot 2
경륜 이
 
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
XpressEngine
 
막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)
연웅 조
 
막하는 스터디 첫 번째 만남 Node.js
막하는 스터디 첫 번째 만남 Node.js막하는 스터디 첫 번째 만남 Node.js
막하는 스터디 첫 번째 만남 Node.js
연웅 조
 
Clean Front-End Development
Clean Front-End DevelopmentClean Front-End Development
Clean Front-End Development
지수 윤
 
Django를 Django답게, Django로 뉴스 사이트 만들기
Django를 Django답게, Django로 뉴스 사이트 만들기Django를 Django답게, Django로 뉴스 사이트 만들기
Django를 Django답게, Django로 뉴스 사이트 만들기
Kyoung Up Jung
 
막하는 스터디 네 번째 만남 AngularJs (20151108)
막하는 스터디 네 번째 만남 AngularJs (20151108)막하는 스터디 네 번째 만남 AngularJs (20151108)
막하는 스터디 네 번째 만남 AngularJs (20151108)
연웅 조
 
React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작
Taegon Kim
 
Nodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjsNodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjs
기동 이
 
React 튜토리얼 2차시
React 튜토리얼 2차시React 튜토리얼 2차시
React 튜토리얼 2차시
태현 김
 
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
NAVER D2
 
Node.js 기본과정
Node.js 기본과정Node.js 기본과정
Node.js 기본과정
Seokyou (Kevin) Hong
 
Node.js
Node.jsNode.js
Node.js
ymtech
 
Node.js 심화과정
Node.js 심화과정Node.js 심화과정
Node.js 심화과정
Seokyou (Kevin) Hong
 
Meteor를 통해서 개발하는 웹어플리케이션 서비스
Meteor를 통해서 개발하는 웹어플리케이션 서비스Meteor를 통해서 개발하는 웹어플리케이션 서비스
Meteor를 통해서 개발하는 웹어플리케이션 서비스
WebFrameworks
 
Django in Production
Django in ProductionDjango in Production
Django in Production
Hyun-woo Park
 
Html5 web workers
Html5 web workersHtml5 web workers
Html5 web workers
Woo Jin Kim
 
React 튜토리얼 1차시
React 튜토리얼 1차시React 튜토리얼 1차시
React 튜토리얼 1차시
태현 김
 
Vue guide예제(vue todo-list)-v0.1
Vue guide예제(vue todo-list)-v0.1Vue guide예제(vue todo-list)-v0.1
Vue guide예제(vue todo-list)-v0.1
DataUs
 
Spring Boot 1
Spring Boot 1Spring Boot 1
Spring Boot 1
경륜 이
 

What's hot (20)

Spring Boot 2
Spring Boot 2Spring Boot 2
Spring Boot 2
 
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
 
막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)
 
막하는 스터디 첫 번째 만남 Node.js
막하는 스터디 첫 번째 만남 Node.js막하는 스터디 첫 번째 만남 Node.js
막하는 스터디 첫 번째 만남 Node.js
 
Clean Front-End Development
Clean Front-End DevelopmentClean Front-End Development
Clean Front-End Development
 
Django를 Django답게, Django로 뉴스 사이트 만들기
Django를 Django답게, Django로 뉴스 사이트 만들기Django를 Django답게, Django로 뉴스 사이트 만들기
Django를 Django답게, Django로 뉴스 사이트 만들기
 
막하는 스터디 네 번째 만남 AngularJs (20151108)
막하는 스터디 네 번째 만남 AngularJs (20151108)막하는 스터디 네 번째 만남 AngularJs (20151108)
막하는 스터디 네 번째 만남 AngularJs (20151108)
 
React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작
 
Nodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjsNodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjs
 
React 튜토리얼 2차시
React 튜토리얼 2차시React 튜토리얼 2차시
React 튜토리얼 2차시
 
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
 
Node.js 기본과정
Node.js 기본과정Node.js 기본과정
Node.js 기본과정
 
Node.js
Node.jsNode.js
Node.js
 
Node.js 심화과정
Node.js 심화과정Node.js 심화과정
Node.js 심화과정
 
Meteor를 통해서 개발하는 웹어플리케이션 서비스
Meteor를 통해서 개발하는 웹어플리케이션 서비스Meteor를 통해서 개발하는 웹어플리케이션 서비스
Meteor를 통해서 개발하는 웹어플리케이션 서비스
 
Django in Production
Django in ProductionDjango in Production
Django in Production
 
Html5 web workers
Html5 web workersHtml5 web workers
Html5 web workers
 
React 튜토리얼 1차시
React 튜토리얼 1차시React 튜토리얼 1차시
React 튜토리얼 1차시
 
Vue guide예제(vue todo-list)-v0.1
Vue guide예제(vue todo-list)-v0.1Vue guide예제(vue todo-list)-v0.1
Vue guide예제(vue todo-list)-v0.1
 
Spring Boot 1
Spring Boot 1Spring Boot 1
Spring Boot 1
 

Similar to Modern web application with meteor

현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)
현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)
현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)
Ukjae Jeong
 
Meteor React Tutorial 따라하기
Meteor React Tutorial 따라하기Meteor React Tutorial 따라하기
Meteor React Tutorial 따라하기
Jiam Seo
 
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
Jeado Ko
 
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 TestOkjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
beom kyun choi
 
알아봅시다, Polymer: Web Components & Web Animations
알아봅시다, Polymer: Web Components & Web Animations알아봅시다, Polymer: Web Components & Web Animations
알아봅시다, Polymer: Web Components & Web Animations
Chang W. Doh
 
Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.
Kyoung Up Jung
 
Django - CRUD 기능 구현
Django - CRUD 기능 구현Django - CRUD 기능 구현
Django - CRUD 기능 구현
Jessica Lee
 
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
중선 곽
 
Express framework tutorial
Express framework tutorialExpress framework tutorial
Express framework tutorial
우림 류
 
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지yongwoo Jeon
 
Polymer따라잡기
Polymer따라잡기Polymer따라잡기
Polymer따라잡기
Han Jung Hyun
 
웹성능최적화 20130405
웹성능최적화 20130405웹성능최적화 20130405
웹성능최적화 20130405주형 전
 
파이썬 플라스크 이해하기
파이썬 플라스크 이해하기 파이썬 플라스크 이해하기
파이썬 플라스크 이해하기
Yong Joon Moon
 
Secrets of the JavaScript Ninja - Chapter 12. DOM modification
Secrets of the JavaScript Ninja - Chapter 12. DOM modificationSecrets of the JavaScript Ninja - Chapter 12. DOM modification
Secrets of the JavaScript Ninja - Chapter 12. DOM modification
Hyuncheol Jeon
 
Spring@mvc웹호스팅
Spring@mvc웹호스팅Spring@mvc웹호스팅
Spring@mvc웹호스팅
J.H Ahn
 
더 나은 웹표준을 위한 Web Components
더 나은 웹표준을 위한 Web Components더 나은 웹표준을 위한 Web Components
더 나은 웹표준을 위한 Web Components정호 전
 
센차 터치2 시작하기 | Devon 2012
센차 터치2 시작하기 | Devon 2012센차 터치2 시작하기 | Devon 2012
센차 터치2 시작하기 | Devon 2012Daum DNA
 
Nexacro
NexacroNexacro
Nexacro
HyungKuIm
 
POSTGRES_사칙연산_익스텐션만들기.pdf
POSTGRES_사칙연산_익스텐션만들기.pdfPOSTGRES_사칙연산_익스텐션만들기.pdf
POSTGRES_사칙연산_익스텐션만들기.pdf
Lee Dong Wook
 

Similar to Modern web application with meteor (20)

현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)
현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)
현대고등학교 PHP 강의 - 7,8차시 (설리번 프로젝트)
 
Meteor React Tutorial 따라하기
Meteor React Tutorial 따라하기Meteor React Tutorial 따라하기
Meteor React Tutorial 따라하기
 
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
[XECon+PHPFest 2014] jQuery 개발자에서 AngularJS 개발자 되기
 
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 TestOkjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
 
알아봅시다, Polymer: Web Components & Web Animations
알아봅시다, Polymer: Web Components & Web Animations알아봅시다, Polymer: Web Components & Web Animations
알아봅시다, Polymer: Web Components & Web Animations
 
Html5
Html5 Html5
Html5
 
Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.
 
Django - CRUD 기능 구현
Django - CRUD 기능 구현Django - CRUD 기능 구현
Django - CRUD 기능 구현
 
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
 
Express framework tutorial
Express framework tutorialExpress framework tutorial
Express framework tutorial
 
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
Html5 앱과 웹사이트를 보다 빠르게 하는 50가지
 
Polymer따라잡기
Polymer따라잡기Polymer따라잡기
Polymer따라잡기
 
웹성능최적화 20130405
웹성능최적화 20130405웹성능최적화 20130405
웹성능최적화 20130405
 
파이썬 플라스크 이해하기
파이썬 플라스크 이해하기 파이썬 플라스크 이해하기
파이썬 플라스크 이해하기
 
Secrets of the JavaScript Ninja - Chapter 12. DOM modification
Secrets of the JavaScript Ninja - Chapter 12. DOM modificationSecrets of the JavaScript Ninja - Chapter 12. DOM modification
Secrets of the JavaScript Ninja - Chapter 12. DOM modification
 
Spring@mvc웹호스팅
Spring@mvc웹호스팅Spring@mvc웹호스팅
Spring@mvc웹호스팅
 
더 나은 웹표준을 위한 Web Components
더 나은 웹표준을 위한 Web Components더 나은 웹표준을 위한 Web Components
더 나은 웹표준을 위한 Web Components
 
센차 터치2 시작하기 | Devon 2012
센차 터치2 시작하기 | Devon 2012센차 터치2 시작하기 | Devon 2012
센차 터치2 시작하기 | Devon 2012
 
Nexacro
NexacroNexacro
Nexacro
 
POSTGRES_사칙연산_익스텐션만들기.pdf
POSTGRES_사칙연산_익스텐션만들기.pdfPOSTGRES_사칙연산_익스텐션만들기.pdf
POSTGRES_사칙연산_익스텐션만들기.pdf
 

Modern web application with meteor

  • 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
 cd sogon2x
  • 4. APP 실행하기 • meteor run 혹은 
 meteor • App running at …메시지 가 나오면 브라우저에서 http://localhost:3000 을 띄운다.
  • 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. HTML TEMPLATE (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. MAIN TEMPLATE 버튼 애드온을 사용하여 입력 창을 만듭니다. http://bootstrapk.com/components/#input- groups-buttons
  • 19. MAIN TEMPLATE <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. MAIN TEMPLATE <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. MAIN TEMPLATE <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. MAIN TEMPLATE <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. MAIN TEMPLATE <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. MAIN TEMPLATE <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. POSTS TEMPLATE •Main 아래 Post들의 목록을 열거하는 화면구성 •media를 사용하여 UI를 먼저 만든다. •http://bootstrapk.com/components/ #media-default
  • 26. POSTS TEMPLATE • main template 아래에 
 {{> posts}} 를 추가하여 posts라는 템플릿을 붙여 주도록한다. <template name="main">
 <div class="container"> …. {{> posts}} </div> </template>
  • 27. POSTS TEMPLATE •Main 아래 Post들의 목록을 열거하는 화면구성 •media를 사용하여 UI를 먼저 만든다. •http://bootstrapk.com/components/ #media-default •client/posts.html 을 생성한다.
  • 28. POSTS TEMPLATE<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. POSTS TEMPLATE<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. POST TEMPLATE 가짜로 자료를 만듭니다. posts.js 안에 반복 구간에 들어갈 값들을 JSON 형태로 만들어봅시다.
  • 33. POST TEMPLATETemplate.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: "배고파서 내가 먹었다."
 }
 ]
 }
 });
  • 34. POST TEMPLATE • posts.html에 반복 구간을 정하고 값을 받을 helper들로 교체합니다.
  • 35. POST TEMPLATE <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>
  • 36. POST TEMPLATE <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>
  • 37. POST TEMPLATE <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>
  • 39. CONNECT DB •lib/collection.js 에 추가
 Posts = new Mongo.Collection('posts'); •client/server 양쪽에 적용 •기존 posts.js 수정
 Template.posts.helpers({
 "posts": function() {
 return Posts.find();
 }
 });
  • 40. CONNECT DB •Browser Console에서 테스트 •Posts.insert({
 author: {
 name: "Master",
 profile_image: "http://lorempixel.com/64/64/cats/"
 },
 message: "집사야 내 밥은 어딨냐?"
 }); •Posts.find().fetch(); •화면과 결과값을 확인
  • 42. REMOVE INSECURE •meteor remove insecure •insert failed: Access denied •사용자가 임의로 데이터 조작을 할 수 없음
  • 43. METHODS •server/methods.js - 서버에서만 insert
 Meteor.methods({
 "addPosts": function(obj) {
 Posts.insert({
 author: {
 name: obj.name,
 profile_image: obj.profile_image
 },
 message: obj.message
 });
 }
 }); 43
  • 44. CLIENT CALL •Method.call 사용. 콘솔에서 테스트.
 Meteor.call("addPosts", {
 name: "Slave4U",
 profile_image: "http://lorempixel.com/ 64/64/people/",
 message: "배고파서 내가 다 먹었다."
 }); 44
  • 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) {
 if (err) {
 throw(err);
 } else {
 console.log(result);
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 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) {
 if (err) {
 throw(err);
 } else {
 console.log(result);
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 46 템플릿 안에서 post라는 id를 가진 객체를 검색. 그 값을 가져온다.
  • 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(err);
 } else {
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 47 method call 후 오류처리
  • 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(err);
 } else {
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 48 처리 성공 후 입력창 내용 삭제
  • 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(err);
 } else {
 template.find('#post').value = "";
 }
 });
 event.preventDefault();
 }
 }); 49 기존 submit 이벤트를 금지 페이지 이동이 안되도록 제한
  • 50. RESET DATABASE •서버 정지 •meteor reset •재기동
 meteor
  • 51. 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()
 });
 }
 }); 51 반드시 서버 시간!
  • 52. SORT BY TIME DESC •시간 역순 정렬. Server 시간 기준 •http://docs.meteor.com/#/full/sortspecifiers •Posts.find({}, {
 sort: {
 createdAt: -1
 }
 });
  • 53. POSTS HELPER •posts.js
 Template.posts.helpers({
 "posts": function() {
 return Posts.find({}, {
 sort: {
 createdAt: -1
 }
 });
 }
 }); 정렬순서
 -1 : 내림차순 1 : 오름차순
  • 55. SESSION •Session의 장점
 전역으로 사용할 수 있다.
 브라우저 콘솔에서 사용이 자유롭다.
 서버 재시작 이후에도 값을 유지한다. •Session의 단점
 전역으로 밖에 사용할 수 없다.
 Deprecated 예정
  • 56. SESSION 사용법 •Session의 읽기 
 Session.get('pageId'); •Session의 쓰기
 Session.set('pageId', 'catLover');
  • 57. 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>
 …
  • 58. SESSION.SET •브라우저 콘솔에서
 Session.set('pageId', 'catLover') •바로 화면이 갱신되는 것을 관찰 •어째서 이렇게 될까?
 Reactive Programming!
 http://docs.meteor.com/#/full/reactivity
  • 59. PUBLISH/SUBSCRIBE • 보고싶은 것만 보고 싶어 요. • meteor remove autopublish
  • 60. AUTOPUBLISH? •insecure 처럼 기본 설치 Meteor package •Collection의 모든 내용을 서버로부터 가져온다. •하지만 우리는 page별로 따로따로 보고 싶다.
  • 63. REMOVE AUTOPUBLISH • meteor remove autopublish • 어? 아무것도 안나와요???
  • 64. DON’T PANIC •원래대로 돌려놓아 봅시다. •server/publish.js 추가
 Meteor.publish('getPage', function() {
 return Posts.find();
 }); •브라우저 콘솔에서 확인해보자
 Meteor.subscribe('getPage');
  • 65. MANUAL SUBSCRIPTION •main.js에 subscribe 추가
 Template.main.onCreated(function() {
 this.subscribe('getPage');
 }); •원래대로 돌아왔다!
  • 66. PUB/SUB BASIC •Server에서 publish 한 데이터를...
 Meteor.publish('publishName', function() {
 return YourCollection.find();
 }); •client에서 subscribe 에서 가져온다.
 Template.yourTemplate.onCreated(function() {
 this.subscribe('publishName');
 }); •간단하죠?
  • 67. PUBLISH WITH PAGEID •조건을 주고 필요한 것들만 가져옵니다.
 (http://docs.meteor.com/#/full/selectors) •server/publish.js 수정
 Meteor.publish('getPage', function(pageId) {
 return Posts.find({pageId: pageId});
 });
  • 68. 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로 가입
  • 69. 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) {
 …
  • 70. 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()
 });
 }
 })
  • 72. KEYWORD별 POSTS •같은 관심사를 가진 사람들끼리 이야기 할 수 있도 록 POSTS를 분리 •채널이나 대화방 같은 느낌 •Page라는 이름으로 분리 •URL로 구분
 /page/keyword
  • 74. WARNING! •Flow-router는 third-party package입니다.
 작성자가 꼭 업데이트를 보증하지 않습니다. •어떤 Router를 사용할지는 선택할 수 있습니다. •Single Page Application에서 Routing(URL 경로)가 꼭 필수이진 않습니다.
  • 75. 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에 기록한다.
  • 77. 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]) - 사용자 생성
  • 78. ACCOUNTS PACKAGES • meteor add accounts- password
 
 E-mail/password 인증 • meteor add ian:accounts- ui-bootstrap-3
 
 bootstrap3용 accounts UI • Template에 {{> loginButtons}}
  • 79. 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
  • 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> 모바일에서 접히는 영역
  • 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> loginButtons 삽입 (MAGIC!!)
  • 83. USER IN METHOD •server/methods.js 에 사용자 정보 적용 •로그인 여부 검사 위해 check 사용
 meteor add check •username은 Meteor.user().username •profile_image는 gravatar를 사용하자
 meteor add jparker:gravatar
  • 84. 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만 전송
  • 85. 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()
 });
 }
 });
  • 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()
 });
 }
 }); 로그인 여부 체크 http://docs.meteor.com/#/full/check
  • 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()
 });
 }
 }); 사용자 ID
  • 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()
 });
 }
 }); Accounts.ui.config에서 받은 사용자 이름
  • 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()
 });
 }
 }); E-Mail 주소로 사용자 Image를 가져옴
  • 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()
 });
 }
 }); (선택사항) 등록된 이미지가 없을 때
 retro 아이콘을 임의로 생성
 https://en.gravatar.com/site/implement/images/
  • 91. 생성일 추가 • 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>
  • 92. 가독성이 떨어진다. 좀 더 친근한 방법으로 표현할 수 없을까?
  • 94. MOMENT •Moment의 Time From을 사용한다.
 http://momentjs.com/docs/#/displaying/ from/ •Template helper로 적용한다.
 http://docs.meteor.com/#/full/ template_helpers
  • 95. 생성일 추가 •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>
 …
  • 97. FACEBOOK/TWITTER • 별다른 행동을 하지 않았는데 가만히 보고 있으면... • 알아서 시간이 변한다. • Reactive Programming 을 활용해서 구현해보자.
  • 98. REACTIVE PROGRAMMING Don’t imperate, Just delcare https://en.wikipedia.org/wiki/Reactive_programming
  • 99. REACTIVE TIME •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라는 키로 고유값을 생성
  • 100. REACTIVE TIME •posts.js
 Template.posts.helpers({
 …
 "timeFrom": function(time) {
 Session.get('live');
 return moment().from(time);
 }
 }); •이때 live의 값이 변경이 없으면 해당 구문을 실행하지 않는다! live를 변경하면 
 timeFrom helper를 재실행
  • 101. REACTIVE COMPUTATION 변경이 있을 때만 실행하여 효율적
  • 103. CURRENTUSER 적용 •main.html - 로그인 사용자만 글을 쓸 수 있게
 {{#if currentUser}}
 <form>
 <div class="input-group">
 ........
 </div>
 </form>
 {{/if}}
  • 105. 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> 접속여부 확인 Following 여부
  • 106. FOLLOW/UNFOLLOW •main.js helper 구현
 사용자가 해당 토픽에 follow하고 있는지 검사
 client에서 기본 접근 가능한 profile 객체를 사용
 'isFollowing': function() {
 var followings = Meteor.user().profile.followings;
 return followings && followings[Session.get('pageId')];
 }
  • 107. 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'));
 }
 });
  • 108. 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
 });
 } 사용자확인
  • 109. DASHBOARD • 현재 사용자의 Follow한 Page를 모아 보는 기능 • Feeling Lucky - 무작위 포스트 이동 기능
  • 110. DASHBOARD •홈 디렉토리 이동 시 Dashboard로 •head.html
 <a class="navbar-brand" href="/">Sogon2x</a> •/ 일때 pageId를 리셋 •client/router.js
 FlowRouter.route('/', {
 action: function() {
 Session.set('pageId');
 }
 });
  • 111. 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}}
  • 112. 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}}
  • 113. 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 이유로 다르게 씀.
  • 114. DASHBOARD •dashboard 화면 구성 •필요한 데이터들을 Publish •Reactive를 이용한 사용자 정보 변경 감지
  • 115. DASHBOARD • 운좋은 예감 - 무작위 Posts 추출
 전체 데이터 갯수-count()이용-를 기준으로 랜덤만큼 skip하 고 limit을 이용해 1개만 값을 find한다. • Meteor.publish('feelingLucky', function() {
 return Posts.find({}, {
 skip: Math.random()*Posts.find().count(),
 limit: 1
 });
 });
  • 116. DASHBOARD •dashboard 생성 시 feelingLucky 를 구독 (subscribe)한다. •client/dashboard.js 생성 후
 Template.dashboard.onCreated(function() {
 this.subscribe('feelingLucky');
 });
  • 117. 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가 없을 때 오류 방지
  • 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;
 }
 }); following 정보를 가져온다.
  • 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;
 }
 }); pageId로 배열로 밀어넣는다.
  • 120. 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들 목록
  • 121. 더 생각해 볼 것들
  • 122. 더 좋은 서비스를 위해 •MongoDB Operator의 사용. (ex: $addToSet, $pull 등) •OAuth를 사용한 외부 서비스(페이스북/네이버/카카오) 로그인 연 동 •수정/삭제 기능 •외부 공유와 검색엔진 최적화 •iOS/Android Hybrid Apps 제작 •Deploy …
  • 123. 참고 사이트 • https://github.com/MeteorKorea/meteor2015codelab
 본 문서의 소스 코드 github 저장소 • http://meteorjs.kr 
 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