CSS Framework과 클라이언트 UI Routing을 위한 라이브러리 설치가 끝났으니 이제 본격적으로 메인화면을 만들어 보도록 하죠. 메인 화면의 디자인은 CSR에서 내용을 참조하여 Twitter Bootstrap의 반응형 웹 디자인 메뉴로 구성한다.
전체 구조 이해하기
우선 메뉴를 만들기 전에 index.html의 전체 구조를 이해하자. SPA 방식의 싱글페이지 애플리케이션 개발이란 index.html의 일부 DOM을 변경함으로써 화면을 전화하여 보여주는 것이다. 초기 index.html 레이아웃은 단순화 하여 사용토록 한다.
- 메뉴 html을 별도 파일로 분리
- 메뉴 html에 링크 설정하기
- 메뉴에 링크된 화면의 html 파일 생성
// index.html 기존 설정
<body ng-app="meanstackApp">
<!-- Add your site or application content here -->
<div class="container" ng-view=""></div>
</body>
1. 메뉴관련 html 파일 분리
우선 메뉴관련 파일을 분리해서 시작한다. app/views/menu.html 파일을 생성하고 index.html 파일에 include 한다.
<!-- Add your site or application content here -->
<!-- menu -->
<div ng-include="' /views/menu.html '"></div>
menu.html 안에 이제 부트스트랩의 반응형 웹 디자인 메뉴를 만들어 보자. 반응형 웹 디자인 메뉴란 해상도에 따라 메뉴의 형태가 자동으로 변하는 것을 말한다. 주로 데스크톱과 모바일기기의 해상도에 맞게 메뉴 형태가 변한다. 처음에 부트스트랩을 직접 수작업을 해보는 것이 도움이 되지만 여기서는 부트스트랩용 저작도구인 Bootply(http://bootply.com/)를 사용해서 만들어 본다.
- Bootply의 "Drag-and-Drop" 가운데 메뉴를 선택한다
- Bootstrap 3.0 에서 "Basic starter"를 선택한다
- html에서 메뉴에 대한 부분만 취한다
menu.html 에서 <div class="navbar navbar-inverse navbar-fixed-top"> 내용은 index.html의 ng-include가 있는 <div>에 class설정한다.
// index.html
<div class="navbar navbar-inverse navbar-fixed-top" ng-include="' /views/menu.html '"></div>
// menu.html
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Brand</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
"grunt serve"로 수행을 해보면 입력한 메뉴 구조를 볼 수 있다. 화면의 사이즈를 줄이면 메뉴형태가 변화는 것 또한 볼 수 있다.
- 데스크톱 해상도
- 모바일 해상도
2. 메뉴의 링크 설정하기
menu.html 파일안에 링크에 대해 설정한다. 요구사항 정의를 보면 MEANStack을 위한 Q&A 서비스를 만드는 것이다. 하기와 같은 대분류 메뉴와 소분류 메뉴를 링크설정해 본다
- 나의 Q&A : 나의 질문 답변, 내가 팔로잉한 사람
- Tech 글 : MEAN Stack 관련 메뉴
- 명예의 전당 : 이번달 명예의 전당(인기글), 멤버소개
부트스트랩은 기본적인 사항은 홈페이지를 통해 몇시간 정도 공부하거나 오픈 튜토리얼 사이트에서 익히고 시작하는 것이 좋다. 그리고 응용에 관련된 부분은 Bootsnipp (http://bootsnipp.com/) 에서 유용한 형태를 취하여 사용해 본다. 여기서는 부트스트랩의 기본적인 Navigation bar 구조를 참조한다.
참조: http://getbootstrap.com/components/#navbar
// menu.html 변경 내용
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"> MEANStack.net</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown"> My Area <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown-header">Status</li>
<li>
<a href="#"> My Level: 평민</a>
</li>
<li>
<a href="#"> 질문: 3, 답변: 1</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Following</li>
<li>
<a href="#"> 윤영식</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown"> Tech Area <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown-header">SPA</li>
<li>
<a href="#"> Angular.js</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Middleware</li>
<li>
<a href="#"> Node.js</a>
</li>
<li>
<a href="#"> Express.js</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">NoSQL</li>
<li>
<a href="#"> MongoDB</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Eco Tools</li>
<li>
<a href="#"> Yeoman</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown"> GuruGuru <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown-header">Ranking</li>
<li>
<a href="#"> 명예의 전당</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Members</li>
<li>
<a href="#"> 멤버소개</a>
</li>
</ul>
</li>
</ul>
</div><!--/.nav-collapse -->
</div>
다시 "grunt serve"를 통하여 메뉴가 정상적으로 만들어 졌는지 확인한다
3. 메뉴 링크에 라우팅 설정하기
메뉴구조를 만들었으니 이제 UI-Router(https://github.com/angular-ui/ui-router) 방식에 따라 라우팅을 설정해 보자. 앵귤러 라우팅 순서는 먼저 서버에서 HTML파일을 불러온 후 앵귤러에서 HTML파일을 파싱하여 앵귤러 코드를 앵귤러 컨텍스트에 포함시키고 DOM을 변경한다.
- 서버에 HTML 파일 요청하기
- HTML 파일 파싱하여 앵귤러 컨텍스트에 포함시키기
- DOM을 변경하여 화면에 표현하기
먼저 "My Level: 평민" 메뉴를 대상으로 설정을 하면 나머지는 동일 과정을 반복하게 된다.
1) mylevel.html 파일을 app/views 폴더 밑에 생성한다
<div class="row">
<div class="col-md-12">
<h1>mylevel.html</h1>
</div>
</div>
2) <li> 태그에 현재 선택이 되면 class="active" 부트스트랩 클래스를 추가하기 위하여 앵귤러의 ng-class를 사용한다.
- 라우킹 명칭을 "mylevel" 로 정한다
- (data-)ng-class 안에 표현식을 넣어서 현재 라우팅 명칭이 mylevel (true)이면 active 클래스를 적용하고 false이면 적용하지 않는다
- ui-sref 는 ui-router에서 사용하는 라우팅 변경 링크 속성으로 href를 대체한다
// 기존 설정
<li>
<a href="#"> My Level: 평민</a>
</li>
// 변경 설정
<li data-ng-class="{ active: $state.includes('mylevel') }">
<a ui-sref="mylevel"> My Level: 평민</a>
</li>
3) 이제 HTML 파일의 위치와 기타 정보를 설정한다
- app/scripts/app.js 가 meanstack 서비스의 메인 애플리케이션 파일이 된다. 앵귤러가 최초 수행이 되면 애플리케이션에서 사용하는 $rootScope를 만들게 되는데 app.js 메인 애플리케이션 레벨에서 사용하는 Global Scope 이다. 단 브라우져를 재로딩하면 초기화 된다.
- ui-router 설정은 홈페이지(https://github.com/angular-ui/ui-router)를 참조한다.
'use strict';
angular.module('meanstackApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ngRoute',
'ui.bootstrap',
'ui.router'
])
.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('main', {
url: '/',
templateUrl: 'views/main.html',
controller: ''
})
.state('mylevel', {
url: '/mylevel',
templateUrl: 'views/mylevel.html',
controller: ''
});
}])
- $urlRouterProvider.otherwise('/') 는 설정되지 않은 uri 요청이 들어올 경우를 처리한다. 여기서는 main 으로 간다
- main.html은 meanstack을 만들 때 생성된 html이다
- 메뉴에서 "나의 레벨"을 클릭하면 "mylevel" state의 url로 변경되고 templateUrl에 설정한 html을 서버에 요청한다
- HTML을 파싱하여 앵귤러 컨텍스트에 포함시키고 앵귤러 코드에 대한 처리는 controller에서 담당하나 아직은 설정하지 않았다
- mylevel.html의 파싱된 DOM은 index.html의 "<div ui-view class="container"></div>" ui-view 하위 <div>에 자동 주입된다.
설정이 완료되었으면 "나의 레벨" 메뉴를 클릭해 본다
ui-view 속성이 있는 <div>에 mylevel.html 파일의 내역의 표현이 약간 위로 올라갔다. index.html과 main.css를 수정한다
// app/index.html 기존
<div class="navbar navbar-inverse navbar-fixed-top" ng-include="' /views/menu.html '"></div>
// app/index.html 수정
<div class="navbar navbar-default navbar-static" ng-include="' /views/menu.html '"></div>
// app/views/main.html 수정
<div class="row">
<div class="col-md-12">
<h1>main.html</h1>
<h2> Welcome to MEANStack.net</h2>
</div>
</div>
// app/sytles/main.css 의 모든 내역을 삭제함
최종 결과 화면
4. 메뉴에 적절한 아이콘 설정하기
다른 메뉴들도 라우팅 명칭을 정하고 menu.html과 app.js에 라우팅 설정을 하고 각 메뉴의 기본 파일을 app/views/ 폴더 밑에 생성하여 연결한다. 그리고 메뉴에 FontAwesome의 아이콘을 설정한다.
- FontAwesome 사이트에서 원하는 아이콘을 선택한다 : http://fortawesome.github.io/Font-Awesome/icons/
- 아이콘 태그를 넣는다 : 예) <i class="fa fa-check-square"></i> fa-check-square
// app/views/menu.html
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span>
</button>
<a ui-sref="main" class="navbar-brand"><i class="fa fa-th-large"></i> MEANStack.net</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-spinner fa-spin"></i> My Area <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li class="dropdown-header">Status</li>
<li data-ng-class="{ active: $state.includes('mylevel') }">
<a ui-sref="mylevel"><i class="fa fa-eye"></i> My Level: 평민</a>
</li>
<li data-ng-class="{ active: $state.includes('myqna') }">
<a ui-sref="myqna"><i class="fa fa-comments"></i> 질문: 3, 답변: 1</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Following</li>
<li data-ng-class="{ active: $state.includes('myfollowing') && $stateParams.userId == '1' }">
<a ui-sref="myfollowing({userId:'1'})"><i class="fa fa-thumb-tack"></i> 박유진</a>
</li>
<li data-ng-class="{ active: $state.includes('myfollowing') && $stateParams.userId == '2' }">
<a ui-sref="myfollowing({userId:'2'})"><i class="fa fa-thumb-tack"></i> 이규원</a>
</li>
<li data-ng-class="{ active: $state.includes('myfollowing') && $stateParams.userId == '3' }">
<a ui-sref="myfollowing({userId:'3'})"><i class="fa fa-thumb-tack"></i> 윤영식</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-pencil-square-o"></i> Tech Area <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown-header">SPA</li>
<li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '1' }">
<a ui-sref="techarea({techId:'1'})"><i class="fa fa-file-text-o"></i> Angular.js</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Middleware</li>
<li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '2' }">
<a ui-sref="techarea({techId:'2'})"><i class="fa fa-file-text-o"></i> Node.js</a>
</li>
<li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '3' }">
<a ui-sref="techarea({techId:'3'})"><i class="fa fa-file-text-o"></i> Express.js</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">NoSQL</li>
<li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '4' }">
<a ui-sref="techarea({techId:'4'})"><i class="fa fa-file-text-o"></i> MongoDB</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Eco Tools</li>
<li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '5' }">
<a ui-sref="techarea({techId:'5'})"><i class="fa fa-file-text-o"></i> Yeoman</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-smile-o"></i> GuruGuru <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="dropdown-header">Ranking</li>
<li data-ng-class="{ active: $state.includes('gururanking') }">
<a ui-sref="gururanking"><i class="fa fa-sitemap"></i> 명예의 전당</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Members</li>
<li data-ng-class="{ active: $state.includes('gurumember') }">
<a ui-sref="gurumember"><i class="fa fa-users "></i> 멤버소개</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
// app/scripts/app.js
angular.module('meanstackApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ngRoute',
'ui.bootstrap',
'ui.router'
])
.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('main', {
url: '/',
templateUrl: 'views/main.html',
controller: ''
})
.state('login', {
url: '/login',
templateUrl: 'views/login.html',
controller: ''
})
.state('mylevel', {
url: '/mylevel',
templateUrl: 'views/mylevel.html',
controller: ''
})
.state('myqna', {
url: '/myqna',
templateUrl: 'views/myqna.html',
controller: ''
})
.state('myfollowing', {
url: '/myfollowing/{userId:[0-9]{1,4}}',
templateUrl: 'views/myfollowing.html',
controller: ''
})
.state('techarea', {
url: '/techarea/{techId:[0-9]{1,4}}',
templateUrl: 'views/techarea.html',
controller: ''
})
.state('gururanking', {
url: '/gururanking',
templateUrl: 'views/gururanking.html',
controller: ''
})
.state('gurumember', {
url: '/gurumember',
templateUrl: 'views/gurumember.html',
controller: ''
});
}])
- ui-router에서 myfollowing({userId:'1'}) 는 파라미터를 넘기는 방식이다 형식) ui-sref='stateName({param:value, param:value})
- ui-router API : https://github.com/angular-ui/ui-router/wiki/Quick-Reference
현재까지 진행된 내역을 GitHub에 올려 놓았다. 다음은 router에 앵귤러 controller를 만들고 router에 설정해 보자
https://github.com/MEAN-STACK/MEANStack-Bookwork/tree/angular_step01_making-index
'AngularJS > Start MEAN Stack' 카테고리의 다른 글
[MEAN Stack] 로그인 서비스 개발 및 정보 저장하기 (0) | 2014.02.04 |
---|---|
[MEAN Stack] Yo 이용하여 Controller 생성하고 UI-Router에 설정하기 (0) | 2014.01.30 |
[MEAN Stack] CSS Framework 및 Angular Library 사전준비하기 (0) | 2014.01.29 |
[MEAN Stack] 프론트앤드 자동화 도구 Yeoman 설치와 사용법 (0) | 2014.01.26 |
[MEAN Stack] Community 서비스 (0) | 2014.01.02 |