Download - Meteor로 만드는 modern web application
Meteor로 만드는 Modern Web Application이재호 (Founder of Appsoulute)[email protected]://github.com/acidsoundhttp://spectrumdig.blogspot.com@acidsound
1. npm install -g meteorite2. mrt create sogon2x
Meteor application create
1. cd sogon2x2. mrt3. http://localhost:3000
Meteor application launch
구현 목표관심사 Page단위 SNS 서비스
1. 화면 생성2. 포스트 입력 저장3. 입력 이벤트 처리4. 포스트 정렬 및 페이지 지정5. 페이지별 라우터 생성6. 스마트 패키지 이용 시간 처리7. 사용자 계정 적용8. 페이지별 가입/탈퇴 처리9. 마이페이지 구현
백문불여일타百聞不如一打
Let's rock
>> client directoryif (Meteor.isClient) {}
>> server directoryif (Meteor.isServer) { Meteor.startup(function () { // code to run on server at startup });}
JS
<body>{{> head}}{{> main}}
</body><template name="head"></template><template name="main"></template>
HTML/시작
Head 1/2<template name="head"> <div class="navbar navbar-fixed-top"> <div class="navbar-inner"> <div class="container"> <!-- .btn-navbar is used as the toggle for collapsed navbar content --> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a>
Head 2/2 <!-- Be sure to leave the brand out there if you want it shown --> <a class="brand" href="/">Sogon</a> <!-- Everything you want hidden at 940px or less, place within here --> <div class="nav-collapse collapse"> <!-- .nav, .navbar-search, .navbar-form, etc --> </div> </div> </div> </div></template>
<template name="main"> <div class="container"> <ul class="unstyled"> <li class="row"> <h2>nobody's Page</h2> <form class="form-inline"> <textarea class="postText input-block-level" placeholder="Press shift+enter to post"></textarea> <button type="reset" class="btn pull-right"><i class="icon-trash"/></button> <button type="submit" class="btn-primary submit btn pull-right"><i class="icon-white icon-pencil"/></button> </form> </li> </ul> </div></template>
Main template
Post template <li class="row post"> <div class="postHead"><span class="label label-success author">User</span><span class="badge timeAgo badge-info pull-right">Now</span> </div> <div class="postBody"> <pre>Tell me something어서 말을 해.<span class="pull-right label label-important tags">page </span></pre> </div> </li>
CSS/* CSS declarations go here */.form-inline button { margin-top: 5px; margin-left: 5px;}form { margin-bottom: 50px;}/* fixed top Scroll */body { padding-top: 60px;}@media (max-width: 979px) { body { padding-top: 0; }}
{{#each posts}} <li class="row post">.... </li> {{/each}}
* 반복 구간
Posts template
Template.main.posts=function() { return [ { text : 'First post' } ];}
Posts
var Posts = new Meteor.Collection('posts');
Posts collection
Template.main.posts=function() { return Posts.find();}
> Posts.insert({'text':'First Post'});
Posts
package 제거mrt remove insecure
거칠게 구현하고 Scaffold 빼내기의 반복
Posts.insert({'text':'say something'});"f595d61e-fad3-4a33-8a19-cfc667e5b672"insert failed: Access denied
Client-side security
Meteor.methods({ "postText": function(text) { if(text) { Posts.insert({'text':text}); } }});
> Meteor.call('postText', 'say something');
Call/Method
Event HandlingTemplate.main.events({ 'submit': function () { var input = $('.postText'); Meteor.call('postText',input.val(), function(err,result) { if(err) throw 'server error'; }); input.val(''); return false; }, 'keydown .postText':function (e) { return (e.shiftKey && e.which === 13) && sendSubmit() || true; }});
sendSubmit으로 refactoring
Session.set('page', '...');
Template.main.pageTitle=function() { return Session.get('page'); } {{#if pageTitle}} <h2>{{pageTitle}}'s Page</h2> {{/if}}
Session
Meteor.call('postText', input.val(), Session.get('page'), function(...
Meteor.methods({ "postText": function(text, page) { if(text) { Posts.insert({'text':text, 'created_at': Date.now(), 'page':page});
Page Call/Method
{{#each posts}} <li class="row post"> <div class="postHead"><span class="label author">User</span><span class="badge timeAgo pull-right">Now</span> </div> <div class="postBody"> <pre>{{{text}}}<span class="pull-right label tags">{{page}} </span></pre> </div> </li> {{/each}}
Page template
Subscribe/Publishmrt remove autopublish
Meteor.autosubscribe(function() { Meteor.subscribe('posts', Session.get('page'));});Template.main.posts=function(){ return Posts.find({}, {sort:{created_at:-1}});};
Meteor.publish('posts', function (page) { return Posts.find({page:page}, {sort:{created_at:-1}}); });
Routermrt add router
<body>{{> head}}{{renderPage}}</body>
비 로그인 시 Title 추가<template name="title"> <div class="container hero-unit"> <h1>Hello Sogon!</h1> <p> Simple and Robust SNS </p> <button class="btn btn-info pull-right">Read More..</button> </div></template>
Router 정의 Meteor.Router.add({ '/':function() { Session.set('page',''); return 'title'; }, '/page/:page':function(args) { Session.set('page',args[0]); return 'main'; } })
Moment
시간을 트위터처럼a few seconds ago, 10 hours ago
Moment 설치mrt add moment> moment().from()"a few seconds ago"> moment(Date.now()-60000).from()"a minute ago"
timeago helperHandlebars.registerHelper('timeago',function(time) { return moment(time).from(); });
<span class="badge pull-right"> {{timeago created_at}} </span>
<template name="head">.... <ul class="nav pull-right"> <li> <a href="#">{{loginButtons}}</a> </li> </ul>
Account
User Collection> Meteor.user() // 현재 접속 유저* login 이전 nullnull* user/password loginid : "<UUID>"emails : Array* facebook loginiid : "<UUID>profile : name : <User Name>
Post with User()Meteor.methods({ "postText": function(text, page) { if(text && page && Meteor.user()) { Posts.insert({'text':text, 'page':page, 'author': Meteor.user(), 'created_at': Date.now() }); } else { throw "access denied"; } }});
Post template <li class="row post"> <div class="postHead"><span class="label label-success author">{{author.profile.name}}</span><span class="badge timeAgo pull-right">{{timeago created_at}}</span>
Form with User()* template main {{#if currentUser}} <li class="row"> <form class="form-inline"> <textarea class="postText input-block-level" placeholder="shift+enter to post.."></textarea> <button type="reset" class="btn pull-right"><i class="icon-trash"/></button> <button type="submit" class="btn-primary submit btn pull-right"><i class="icon-white icon-pencil"/></button> </form> </li> {{/if}}
JSON Key/Value 구조user()ㄴ profile ㄴ subscribers ㄴ page1 ㄴ timestamp ㄴ page2 ㄴ timestamp>> 가입 여부 확인!!Subscribers['page1'] -> 있으면 true 없으면 null이니까 false
>> 검색Posts.find({page: {$in : [ 유저가 가입한 Page들의 이름 Array ]});
Subscribers 구조
MethodSubscribe/Unsubscribe "subscribe": function(page) { var subscribers={}; subscribers["profile.subscribers."+page]={ dateTime:Date.now() }; Meteor.users.update(this.userId, {$set:subscribers}); }, "unsubscribe": function(page) { var subscribers={}; subscribers["profile.subscribers."+page]=false; Meteor.users.update(this.userId, {$unset:subscribers}); }
Helper Subscribe/Unsubscribe Template.main.helpers({ 'isSubscribe': function (subscribers) { return subscribers && subscribers[Session.get('page')]; } });
Template Subscribe/Unsubscribe <h2>{{pageTitle}}'s page {{#if currentUser}} {{#unless isSubscribe currentUser.profile.subscribers}} <button class="btn btn-primary subscribe"> Subscribe </button> {{else}} <button class="btn btn-inverse unsubscribe"> Unsubscribe </button> {{/unless}} {{/if}}</h2>
'click .subscribe' : function () { Meteor.call('subscribe', Session.get('page')) }, 'click .unsubscribe' : function () { Meteor.call('unsubscribe', Session.get('page')) }
Event subscribe/unsubscribe
Posts collection subscribePage 에서 볼때 page 기준유저의 MyPage 에선 User 하는 기준으로 following
Meteor.autosubscribe(function() { Meteor.subscribe('posts', Session.get('page'), Meteor.user()); });
Posts collection publishMeteor.publish('posts', function (page, user) { return Posts.find( page && {page:page} || user && { page:{$in: _.map(user.profile && user.profile.subscribers, function(v,k) { return k; }) } } || {}, {sort:{created_at:-1}} );});
내가 Subscribe 한 곳의 글을 모아볼 수 있게template main 에서 {{#each posts}} 부분을 posts template 으로 분리
MyPage
MyPage template<template name="posts"> {{#each posts}}... {{/each}}</template>
<template name="mypage"> <div class="container"> <ul class="unstyled"> <li> <h2>My page</h2> </li> {{> posts}} </ul> </div></template>
Posts CollectionTemplate.main.posts=function(){ return Posts.find({}, {sort:{created_at:-1}});};에서
Template.posts.posts=function(){ return Posts.find({}, {sort:{created_at:-1}});};로 변경
MyPage RouterMeteor.Router.add({ '/':function() { Session.set('page',''); return 'title'; }, '/page/:page':function(args) { Session.set('page', args[0]); return 'main'; }, '/mypage':function() { Session.set('page',''); return 'mypage'; }});
MyPage filterMeteor.Router.filters({ 'login' : function() { if (Meteor.user()) { Session.set('page', ''); return 'mypage'; } else { return 'title'; } }});Meteor.Router.filter('login', {only: 'title'});
Posts template link<template name="posts"> {{#each posts}} <li class="row post"> <div class="postHead"><span class="label label-success author">{{author.profile.name}}</span><span class="badge badge-info timeAgo pull-right">{{timeago created_at}}</span> </div> <div class="postBody"> <pre>{{{text}}} <a href="/page/{{page}}"><span class="pull-right label label-important tags">{{page}} </span></a></pre> </div> </li> {{/each}}</template>
ONE MORE THING?
less?mrt add less> sogon.css를 sogon.less로 변경http://www.bootstrapcdn.com/#bootswatch 중//netdna.bootstrapcdn.com/bootswatch/2.1.0/amelia/bootstrap.min.css 를 적용해보자.> theme를 적용해보자!@import "http://netdna.bootstrapcdn.com/bootswatch/2.1.0/united/bootstrap.min.css";
FORK ME!!PULL ME!!http://github.com/acidsound/sogon2x