124
MODERN WEB APPLICATION WITH METEOR 이재호 Appsoulute 대표 [email protected] http://github.com/acidsound http://spectrumdig.blogspot.kr

Meteor2015 codelab

Embed Size (px)

Citation preview

Page 1: Meteor2015 codelab

MODERN WEB APPLICATIONWITH METEOR

이재호Appsoulute 대표

[email protected]://github.com/acidsound

http://spectrumdig.blogspot.kr

Page 2: Meteor2015 codelab

INSTALL METEORLinux/OS X curl https://install.meteor.com/ | sh Windows https://install.meteor.com/windows

Page 3: Meteor2015 codelab

첫 METEOR APP

• meteor로 시작하는 명령은 터미널이나 커맨드라인(시작>실행>cmd)에서 입력합니다.

• meteor create sogon2x

Page 4: Meteor2015 codelab

APP 실행하기

• meteor run / meteor

Page 5: Meteor2015 codelab

구현 목표관심사 기반 마이크로 블로깅 서비스

1. 화면생성

2. 포스트 입력

3. 이벤트 처리

4. 포스트 정렬

5. 사용자 계정

6. 구독/탈퇴

7. 대쉬보드

Page 6: Meteor2015 codelab

백문불여일타(百聞不如一打)한타 한타 시작해봅시다

Page 7: Meteor2015 codelab

TOOL

• 어떤 걸로 코드를 만드실 건가요?

• ATOM (무료 추천!)

• Sublime text (인기!)

• Webstorm (유료 최고!)

Page 8: Meteor2015 codelab

JAVASCRIPT 구조CLIENT

if (Meteor.isClient) {

}SERVER

if (Meteor.isServer) {

Meteor.startup(function() {

// code to run on server at startup

});

}

사용자 브라우저에서실행합니다.

서버에서실행합니다.

Page 9: Meteor2015 codelab

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

Page 10: Meteor2015 codelab

HEAD• https://atmospherejs.com/twbs/bootstrap

• meteor add twbs:bootstrap

• 적용 후 변화를 관찰

• navbar 사용http://bootstrapk.com/components/#navbar-brand-image

Page 11: Meteor2015 codelab

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>

Page 12: Meteor2015 codelab

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> 상단 네비게이션 바

Page 13: Meteor2015 codelab

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도 시도

Page 14: Meteor2015 codelab

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)

Page 15: Meteor2015 codelab

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

Page 16: Meteor2015 codelab

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 영역

Page 17: Meteor2015 codelab

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> 로고 영역

Page 18: Meteor2015 codelab

MAIN TEMPLATE

버튼 애드온을 사용하여 입력 창을 만듭니다.

http://bootstrapk.com/components/#input-groups-buttons

Page 19: Meteor2015 codelab

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>

Page 20: Meteor2015 codelab

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

Page 21: Meteor2015 codelab

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> 폼 요소

Page 22: Meteor2015 codelab

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

Page 23: Meteor2015 codelab

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

Page 24: Meteor2015 codelab

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

Page 25: Meteor2015 codelab

POST TEMPLATE

• Main 아래 Post들의 목록을 열거하는 화면구성

• media를 사용하여 UI를 먼저 만든다.

• http://bootstrapk.com/components/#media-default

Page 26: Meteor2015 codelab

POST TEMPLATE

• main template 아래에 {{> posts}} 를 추가하여 posts라는 템플릿을 붙여주도록한다.

<template name="main"> <div class="container">

….

{{> posts}}

</div>

</template>

Page 27: Meteor2015 codelab

POST TEMPLATE

• Main 아래 Post들의 목록을 열거하는 화면구성

• media를 사용하여 UI를 먼저 만든다.

• http://bootstrapk.com/components/#media-default

Page 28: Meteor2015 codelab

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> <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>

Page 29: Meteor2015 codelab

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> <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>

반복구간

Page 30: Meteor2015 codelab

POST TEMPLATE{{#each posts}}

….

{{/each}}

• 반복 구간 처리

Page 31: Meteor2015 codelab

이제 코딩을 합시다.Let’s Do Some Coding!

Page 32: Meteor2015 codelab

일단 이사 먼저!

• client 폴더를 만듭니다.

• 지금까지 만든 모든 html파일들을 client 아래로 이동합니다.

• 같은 곳에 posts.js를 만들어줍니다.

Page 33: Meteor2015 codelab

POST TEMPLATE

가짜로 자료를 만듭니다.

posts.js 안에 반복 구간에 들어갈 값들을 JSON 형태로 만들어봅시다.

Page 34: Meteor2015 codelab

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: "배고파서 내가 먹었다." } ] } });

Page 35: Meteor2015 codelab

POST TEMPLATE

• posts.html에 반복 구간을 정하고 값을 받을 helper들로 교체합니다.

Page 36: Meteor2015 codelab

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>

Page 37: Meteor2015 codelab

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>

Page 38: Meteor2015 codelab

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>

Page 39: Meteor2015 codelab

중간 결과물Mobile과 Desktop동일하게 나옵니까?

Page 40: Meteor2015 codelab

CONNECT DB• lib/collection.js 에 추가

Posts = new Mongo.Collection('posts');

• client/server 양쪽에 적용

• 기존 posts.js 수정Template.posts.helpers({ "posts": function() { return Posts.find(); } });

Page 41: Meteor2015 codelab

CONNECT DB• Browser Console에서 테스트

• Posts.insert({ author: { name: "Master", profile_image: "http://lorempixel.com/64/64/cats/" }, message: "집사야 내 밥은 어딨냐?"});

• Posts.find().fetch();

• 화면과 결과값을 확인

Page 42: Meteor2015 codelab

SERVER METHOD보안이 필요한 시기

Page 43: Meteor2015 codelab

REMOVE INSECURE

• meteor remove insecure

• insert failed: Access denied

• 사용자가 임의로 데이터 조작을 할 수 없음

Page 44: Meteor2015 codelab

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

Page 45: Meteor2015 codelab

CLIENT CALL

• Method.call 사용. 콘솔에서 테스트.Meteor.call("addPosts", { name: "Slave4U", profile_image: "http://lorempixel.com/64/64/people/", message: "배고파서 내가 다 먹었다."});

45

Page 46: Meteor2015 codelab

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

사용자 로그인과연동 필요

Page 47: Meteor2015 codelab

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를 가진 객체를 검색.그 값을 가져온다.

Page 48: Meteor2015 codelab

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 후 오류처리

Page 49: Meteor2015 codelab

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

처리 성공 후 입력창 내용 삭제

Page 50: Meteor2015 codelab

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 이벤트를 금지페이지 이동이 안되도록 제한

Page 51: Meteor2015 codelab

RESET DATABASE

• 서버 정지

• meteor reset

• 재기동

Page 52: Meteor2015 codelab

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

반드시 서버 시간!

Page 53: Meteor2015 codelab

SORT BY TIME DESC• 시간 역순 정렬. Server 시간 기준

• http://docs.meteor.com/#/full/sortspecifiers

• Posts.find({}, { sort: { createdAt: -1 } });

Page 54: Meteor2015 codelab

POSTS HELPER• posts.js

Template.posts.helpers({ "posts": function() { return Posts.find({}, { sort: { createdAt: -1 } }); } });

정렬순서-1 : 내림차순1 : 오름차순

Page 55: Meteor2015 codelab

SESSIONinsecure처럼

편리하지만 버려야할 것

계륵(鷄肋)…하지만 맛있다

Page 56: Meteor2015 codelab

SESSION• Session의 장점전역으로 사용할 수 있다.브라우저 콘솔에서 사용이 자유롭다.서버 재시작 이후에도 값을 유지한다.

• Session의 단점전역으로 밖에 사용할 수 없다.Deprecated 예정

Page 57: Meteor2015 codelab

SESSION 사용법

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

• Session의 쓰기Session.set('pageId', 'catLover');

Page 58: Meteor2015 codelab

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> …

Page 59: Meteor2015 codelab

SESSION.SET

• 브라우저 콘솔에서Session.set('pageId', 'catLover')

• 바로 화면이 갱신되는 것을 관찰

• 어째서 이렇게 될까?Reactive Programming!http://docs.meteor.com/#/full/reactivity

Page 60: Meteor2015 codelab

PUBLISH/SUBSCRIBE

• 보고싶은 것만 보고 싶어요.

• meteor remove autopublish

Page 61: Meteor2015 codelab

AUTOPUBLISH?

• insecure 처럼 기본 설치 Meteor package

• Collection의 모든 내용을 서버로부터 가져온다.

• 하지만 우리는 page별로 따로따로 보고 싶다.

Page 62: Meteor2015 codelab

BEFOREdefault Autopublish

Page 63: Meteor2015 codelab

AFTERwith Publish/Subscribe

(https://www.discovermeteor.com/blog/understanding-meteor-

publications-and-subscriptions/)

Page 64: Meteor2015 codelab

REMOVE AUTOPUBLISH

• meteor remove autopublish

• 어? 아무것도 안나와요?????

Page 65: Meteor2015 codelab

DON’T PANIC• 원래대로 돌려놓아 봅시다.

• server/publish.js 추가Meteor.publish('getPage', function() { return Posts.find();});

• 브라우저 콘솔에서 확인해보자Meteor.subscribe('getPage');

Page 66: Meteor2015 codelab

MANUAL SUBSCRIPTION

• main.js에 subscribe 추가Template.main.onCreated(function() { this.subscribe('getPage');});

• 원래대로 돌아왔다!

Page 67: Meteor2015 codelab

PUB/SUB BASIC• Server에서 publish 한 데이터를...

Meteor.publish('publishName', function() { return YourCollection.find();});

• client에서 subscribe 에서 가져온다.Template.yourTemplate.onCreated(function() { this.subscribe('publishName');});

• 간단하죠?

Page 68: Meteor2015 codelab

PUBLISH WITH PAGEID

• 조건을 주고 필요한 것들만 가져옵니다.(http://docs.meteor.com/#/full/selectors)

• server/publish.js 수정Meteor.publish('getPage', function(pageId) { return Posts.find({pageId: pageId});});

Page 69: Meteor2015 codelab

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로 가입

Page 70: Meteor2015 codelab

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) { …

Page 71: Meteor2015 codelab

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

Page 72: Meteor2015 codelab

ROUTER어디로 가야하나요?

콘솔에서 Session.set은 그만

Page 73: Meteor2015 codelab

KEYWORD별 POSTS• 같은 관심사를 가진 사람들끼리 이야기 할 수 있도록

POSTS를 분리

• 채널이나 대화방 같은 느낌

• Page라는 이름으로 분리

• URL로 구분 /page/keyword

Page 74: Meteor2015 codelab

ROUTING

• Routing용 package 설치

• meteor add kadira:flow-router

Page 75: Meteor2015 codelab

WARNING!

• Flow-router는 third-party package입니다.작성자가 꼭 업데이트를 보증하지 않습니다.

• 어떤 Router를 사용할지는 선택할 수 있습니다.

• Single Page Application에서 Routing(URL 경로)가 꼭 필수이진 않습니다.

Page 76: Meteor2015 codelab

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에 기록한다.

Page 77: Meteor2015 codelab

ACCOUNTSmeteor add accounts-

password

사용자를 만들자

Page 78: Meteor2015 codelab

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]) - 사용자 생성

Page 79: Meteor2015 codelab

ACCOUNTS PACKAGES

• meteor add accounts-passwordE-mail/password 인증

• meteor add ian:accounts-ui-bootstrap-3bootstrap3용 accounts UI

• Template에 {{> loginButtons}}

Page 80: Meteor2015 codelab

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

Page 81: Meteor2015 codelab

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>

모바일에서 접히는 영역

Page 82: Meteor2015 codelab

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!!)

Page 83: Meteor2015 codelab

USERNAME• 사용자명 추가

• https://github.com/ianmartorell/meteor-accounts-ui-bootstrap-3/#custom-signup-options

• client/config.jsAccounts.ui.config({ extraSignupFields: [{ fieldName: "username", fieldLabel: "username", inputType: 'text' }]});

추가 입력 필드

Page 84: Meteor2015 codelab

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

• 로그인 여부 검사 위해 check 사용 meteor add check

• username은 Meteor.user().username

• profile_image는 gravatar를 사용하자meteor add jparker :gravatar

Page 85: Meteor2015 codelab

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만 전송

Page 86: Meteor2015 codelab

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

Page 87: Meteor2015 codelab

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

Page 88: Meteor2015 codelab

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

Page 89: Meteor2015 codelab

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에서 받은사용자 이름

Page 90: Meteor2015 codelab

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를 가져옴

Page 91: Meteor2015 codelab

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/

Page 92: Meteor2015 codelab

생성일 추가• 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>

Page 93: Meteor2015 codelab

가독성이 떨어진다.좀 더 친근한 방법으로 표현할 수 없을까?

Page 94: Meteor2015 codelab

MOMENT

• 글별 상대시간 표시

• meteor add momentjs:moment

Page 95: Meteor2015 codelab

MOMENT

• Moment의 Time From을 사용한다.http://momentjs.com/docs/#/displaying/from/

• Template helper로 적용한다.http://docs.meteor.com/#/full/template_helpers

Page 96: Meteor2015 codelab

생성일 추가• 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> …

Page 97: Meteor2015 codelab

REACTIVE살아있는 실시간 값

Page 98: Meteor2015 codelab

FACEBOOK/TWITTER

• 별다른 행동을 하지 않았는데 가만히 보고 있으면...

• 알아서 시간이 변한다.

• Reactive Programming 을 활용해서 구현해보자.

Page 99: Meteor2015 codelab

REACTIVE PROGRAMMINGDon’t imperate, Just delcare

https://en.wikipedia.org/wiki/Reactive_programming

Page 100: Meteor2015 codelab

REACTIVE TIME• meteor add random 패키지 추가

• posts.jsTemplate.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라는 키로 고유값을 생성

Page 101: Meteor2015 codelab

REACTIVE TIME• posts.js

Template.posts.helpers({ … "timeFrom": function(time) { Session.get('live'); return moment().from(time); } });

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

live를 변경하면 timeFrom helper를 재실행

Page 102: Meteor2015 codelab

REACTIVE COMPUTATION변경이 있을 때만 실행하여 효율적

Page 103: Meteor2015 codelab

LOGIN 여부

• ClientMeteor.userId()

• Server this.userId()

• Template{{#if currentUser}}

Page 104: Meteor2015 codelab

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

{{#if currentUser}} <form> <div class="input-group"> ........ </div> </form> {{/if}}

Page 105: Meteor2015 codelab

FOLLOW/UNFOLLOW관심사 추적

Page 106: Meteor2015 codelab

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 여부

Page 107: Meteor2015 codelab

FOLLOW/UNFOLLOW• main.js helper 구현사용자가 해당 토픽에 follow하고 있는지 검사 client에서 기본 접근 가능한 profile 객체를 사용 'isFollowing': function() { var followings = Meteor.user().profile.followings; return followings && followings[Session.get('pageId')];}

Page 108: Meteor2015 codelab

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

Page 109: Meteor2015 codelab

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

사용자확인

Page 110: Meteor2015 codelab

DASHBOARD

• 현재 사용자의 Follow한 Page를 모아 보는 기능

• Feeling Lucky - 무작위 포스트 이동 기능

Page 111: Meteor2015 codelab

DASHBOARD• 홈 디렉토리 이동 시 Dashboard로

• head.html<a class="navbar-brand" href="/">Sogon2x</a>

• / 일때 pageId를 리셋

• client/router.jsFlowRouter.route('/', { action: function() { Session.set('pageId'); } });

Page 112: Meteor2015 codelab

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}}

Page 113: Meteor2015 codelab

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}}

Page 114: Meteor2015 codelab

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 이유로 다르게 씀.

Page 115: Meteor2015 codelab

DASHBOARD

• dashboard 화면 구성

• 필요한 데이터들을 Publish

• Reactive를 이용한 사용자 정보 변경 감지

Page 116: Meteor2015 codelab

DASHBOARD• 운좋은 예감 - 무작위 Posts 추출전체 데이터 갯수-count()이용-를 기준으로 랜덤만큼 skip하고 limit을 이용해 1개만 값을 find한다.

• Meteor.publish('feelingLucky', function() { return Posts.find({}, { skip: Math.random()*Posts.find().count(), limit: 1 });});

Page 117: Meteor2015 codelab

DASHBOARD

• dashboard 생성 시 feelingLucky 를 구독(subscribe)한다.

• client/dashboard.js 생성 후Template.dashboard.onCreated(function() { this.subscribe('feelingLucky');});

Page 118: Meteor2015 codelab

DASHBOARD• helper 정보 - luckyPage / pages

• dashboard.jsTemplate.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가 없을 때 오류 방지

Page 119: Meteor2015 codelab

DASHBOARD• helper 정보 - luckyPage / pages

• dashboard.jsTemplate.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 정보를 가져온다.

Page 120: Meteor2015 codelab

DASHBOARD• helper 정보 - luckyPage / pages

• dashboard.jsTemplate.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로 배열로 밀어넣는다.

Page 121: Meteor2015 codelab

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들 목록

Page 122: Meteor2015 codelab

더 생각해 볼 것들

Page 123: Meteor2015 codelab

더 좋은 서비스를 위해• MongoDB Operator의 사용. (ex: $addToSet, $pull 등)

• OAuth를 사용한 외부 서비스(페이스북/네이버/카카오) 로그인 연동

• 수정/삭제 기능

• 외부 공유와 검색엔진 최적화

• iOS/Android Hybrid Apps 제작

• Deploy …

Page 124: Meteor2015 codelab

참고 사이트• https://github.com/MeteorKorea/meteor2015codelab본 문서의 소스 코드 github 저장소

• http://meteorjs.rk Meteor Korea

• http://www.meetup.com/Meteor-SeoulMeteor Seoul Meetup 모임

• http://kr.discovermeteor.com/Discover Meteor 한글

• https://www.facebook.com/groups/meteorschool/Facebook Meteor School