블로그 이미지
Peter Note
Web & LLM FullStacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

포틀릿을 생성하고 세부사항에 대하여 알아보고, 포틀릿의 두가지 Phase를 이해하자. 이를 위해 Maven 기반으로 포틀릿을 생성하고 Java와 JSP를 생성/수정해 보도록 한다. 

완성된 포틀릿의 edit 모습

 


포틀릿 생성

  - 이클립스 IDE를 통한 생성

  - CLI 명령으로 통한 생성

// CLI 생성

// 이름의 뒤에 자동으로 "-portlet"이 붙는다 

$ cd ~/development/liferay_portal/plugins-sdk-6.2/portlets

$ create.sh mobiconsoft-greeting "hi youngsik"

Buildfile: /Users/xxx/development/liferay_portal/plugins-sdk-6.2/portlets/build.xml

.. 중략 ..

BUILD SUCCESSFUL

Total time: 1 second


// 배포

$ ant deploy 

Buildfile: /Users/xxx/development/liferay_portal/plugins-sdk-6.2/portlets/build.xml

deploy:

    .. 중략 ..

     [copy] Copying 1 file to /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/deploy

BUILD SUCCESSFUL

Total time: 9 seconds


* 만일 tomcat ROOT를 변경하였다면 deploy/*.war는 기본 폴더인 {TOMCAT_HOME}/webapps/ 밑으로 들어간다. 

tomcat을 수행하기전에 deploy/*.war 파일을 변경된 폴더 밑으로 copy한 후 시작한다. (변경된 폴더로 deploy하는 설정을 못 찾겠음)



Portlet 구조이해

  - 톰켓에 배포된 디렉토리 구조

> 자바 소스, 웹 리소스, 환경 설정 3가지로 구성되어있다.

  톰켓의 단일 Context로 운영된다. 즉, J2EE Context 폴더 구조임 


> xml 환경파일들

 portlet.xml : JSR-286 Portlet 스펙에 대한 환경파일

 liferay-display.xml : 화면구성 위저드에서 카테고리 지정 

 liferay-plugin-package.properties : hot deploy 설정

 liferay-portlet.xml : liferay portal server와 특화된 portlet 설정들

 

> 리소스들

 html : 클라이언트에 표현되는 것으로 <html>, <head> 태그는 없어야 함

 css, js : 다른 css와 충돌하지 않도록 namespace를 준다 


  - portlet.xml

    + MVCPortlet : 포틀릿 전체 기능이 내장된 놈. 우리가 만드는 포틀릿은 실제 요것을 상속받아서 구현된다. 

    + portlet-info : 카테고리되었을 때 이름 지정 

<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" version="2.0">

<portlet>

<portlet-name>mobiconsoft-greeting</portlet-name>

<display-name>hi youngsik</display-name>

<portlet-class>com.liferay.util.bridges.mvc.MVCPortlet</portlet-class>

<init-param>

<name>view-template</name>

<value>/view.jsp</value>

</init-param>

<expiration-cache>0</expiration-cache>

<supports>

<mime-type>text/html</mime-type>

</supports>

<portlet-info>

<title>hi youngsik</title>

<short-title>hi youngsik</short-title>

<keywords>hi youngsik</keywords>

</portlet-info>

<security-role-ref>

<role-name>administrator</role-name>

</security-role-ref>

<security-role-ref>

<role-name>guest</role-name>

</security-role-ref>

<security-role-ref>

<role-name>power-user</role-name>

</security-role-ref>

<security-role-ref>

<role-name>user</role-name>

</security-role-ref>

</portlet>

</portlet-app>


  - liferay-portlet.xml 

    + header-portlet-css : <header> 태그에 들어갈 css 

    + footer-portlet-javascripit : </body> 끝에 들어갈 javascript 

    + instanceable : 해당 포틀릿이 한페이지 멀티로 나오는 인스턴스들인지 true / false 설정

<?xml version="1.0"?>

<!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 6.2.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_6_2_0.dtd">


<liferay-portlet-app>

<portlet>

<portlet-name>mobiconsoft-greeting</portlet-name>

<icon>/icon.png</icon>

<header-portlet-css>/css/main.css</header-portlet-css>

<footer-portlet-javascript>/js/main.js</footer-portlet-javascript>

<css-class-wrapper>mobiconsoft-greeting-portlet</css-class-wrapper>

</portlet>

<role-mapper>

<role-name>administrator</role-name>

<role-link>Administrator</role-link>

</role-mapper>

<role-mapper>

<role-name>guest</role-name>

<role-link>Guest</role-link>

</role-mapper>

<role-mapper>

<role-name>power-user</role-name>

<role-link>Power User</role-link>

</role-mapper>

<role-mapper>

<role-name>user</role-name>

<role-link>User</role-link>

</role-mapper>

</liferay-portlet-app>



포틀릿을 수정해 보기 

  - liferay-portal.xml 에서 instanceable=false 추가 

<instanceable>false</instanceable>

  - view.jsp 파일 수정 

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>

<%@ page import="javax.portlet.PortletPreferences" %>


<portlet:defineObjects />


<%

PortletPreferences prefs = renderRequest.getPreferences();

String greeting = (String)prefs.getValue("greeting", "Hello! Welcome to our portal.");

%>


<p><%= greeting %></p>


<portlet:renderURL var="editGreetingURL">

    <portlet:param name="mvcPath" value="/edit.jsp" />

</portlet:renderURL>


<p><a href="<%= editGreetingURL %>">Edit greeting</a></p>

  - edit.jsp 파일 신규 추가

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>

<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>


<%@ page import="javax.portlet.PortletPreferences" %>


<portlet:defineObjects />


<%

PortletPreferences prefs = renderRequest.getPreferences();

String greeting = renderRequest.getParameter("greeting");

if (greeting != null) {

    prefs.setValue("greeting", greeting);

    prefs.store();

%>

    <p>Greeting saved successfully!</p>

<%

}

%>


<%

greeting = (String)prefs.getValue("greeting", "Hello! Welcome to our portal.");

%>


<portlet:renderURL var="editGreetingURL">

    <portlet:param name="mvcPath" value="/edit.jsp" />

</portlet:renderURL>


<aui:form action="<%= editGreetingURL %>" method="post">

    <aui:input label="greeting" name="greeting" type="text" value="<%=greeting %>" />

    <aui:button type="submit" />

</aui:form>


<portlet:renderURL var="viewGreetingURL">

    <portlet:param name="mvcPath" value="/view.jsp" />

</portlet:renderURL>


<p><a href="<%= viewGreetingURL %>">&larr; Back</a></p>

  - {SDK}/portlets 밑에서 수정했으면 다시 deploy를 이미 www 폴더에서 수정했다면 tomcat 다시 restart 한다.

// 수정한 view.jsp 


// 신규 추가한 edit.jsp 



  - *.jsp 에서 중요한 사항

    +  http://java.sun.com/portlet_2_0 에서 정의한 <portlet:renderURL> 태그(taglib)를 사용한다  

    +  edit.jsp에서 aui(AlloyUI == YUI3)를 이용하여 form을 만들고 있다 

    + <portlet:defineObjects/> 를 넣으면 renderRequest, portletConfig, portletPreferences 를 jsp에서 사용할 수 있다. 이것은 jsp에서만 유효하고 여러가지 오브젝트를 사용토록 해준다 

RenderRequest renderRequest: represents the request sent to the portlet to handle a render. renderRequest is only available to a JSP if the JSP was included during the render request phase.


ResourceRequest resourceRequest: represents the request sent to the portlet for rendering resources. resourceRequest is only available to a JSP if the JSP was included during the resource-serving phase.


ActionRequest actionRequest: represents the request sent to the portlet to handle an action. actionRequest is only available to a JSP if the JSP was included during the action-processing phase.


EventRequest eventRequest: represents the request sent to the portlet to handle an event. eventRequest is only available to a JSP if the JSP was included during the event-processing phase.


RenderResponse renderResponse: represents an object that assists the portlet in sending a response to the portal. renderResponse is only available to a JSP if the JSP was included during the render request phase.


ResourceResponse resourceResponse: represents an object that assists the portlet in rendering a resource. resourceResponse is only available to a JSP if the JSP was included in the resource-serving phase.


ActionResponse actionResponse: represents the portlet response to an action request. actionResponse is only available to a JSP if the JSP was included in the action-processing phase.


EventResponse eventResponse: represents the portlet response to an event request. eventResponse is only available to a JSP if the JSP was included in the event-processing phase.


PortletConfig portletConfig: represents the portlet’s configuration including, the portlet’s name, initialization parameters, resource bundle, and application context. portletConfig is always available to a portlet JSP, regardless of the request-processing phase in which it was included.


PortletSession portletSession: provides a way to identify a user across more than one request and to store transient information about a user. A portletSession is created for each user client. portletSession is always available to a portlet JSP, regardless of the request-processing phase in which it was included. portletSession is null if no session exists.


Map<String, Object> portletSessionScope: provides a Map equivalent to the PortletSession.getAtrributeMap() call or an empty Map if no session attributes exist.


PortletPreferences portletPreferences: provides access to a portlet’s preferences. portletPreferences is always available to a portlet JSP, regardless of the request-processing phase in which it was included.


Map<String, String[]> portletPreferencesValues: provides a Map equivalent to the portletPreferences.getMap() call or an empty Map if no portlet preferences exist.



Liferay IDE를 통한 개발 (주의사항)

  - 기본 {TOMCAT_HOME}/webapps/ROOT 를 사용한다. 

  - 기존 ~/liferay_portal/www 에서 다시 원위치로 설정한다.

1) {TOMCAT_HOME}/conf.xml 에서 위치 변경

<Host appBase="/Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/webapps" autoDeploy="true" name="localhost" unpackWARs="true">


2) {PLUGIN_SDK}/build.<username>.properties

app.server.tomcat.lib.global.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/lib/ext

app.server.tomcat.deploy.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/webapps

app.server.parent.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2

app.server.tomcat.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42

app.server.type = tomcat

app.server.tomcat.portal.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/webapps/ROOT

  - 또한 Maven으로 생성시 pom.xml에서 <version> 은 "1.0.0-SNAPSHOT"이 아니라 "portlet" 이라고 준다 



포틀릿의 2 Phase Execution 이해 

  - Action Phase와 Render Phase로 구성된다

  - Action Phase

    + 하나의 포틀릿에서만 유저 인터렉션이 일어날 수있다. 

    + 사용자의 prefereneces는 한번만 변경되고 재변경되지 않는다 

  - Render Phase

    + action phase가 있은 후 모든 포틀릿의 render phase를 호출한다.

1) action phase를 만들기위해서 기존에 Eclipse에서 생성한 "mobiconsoft-sample"에서 자바소스를 추가한다 

    (CLI 방식일 경우 : {SDK}/porlets/mobiconsoft-sample-portlet/WEB-INF/src 밑에 둔다)

     MVCPorlet을 상속받는다 

package com.mobiconsoft.sample;


import java.io.IOException;

import javax.portlet.ActionRequest;

import javax.portlet.ActionResponse;

import javax.portlet.PortletException;

import javax.portlet.PortletPreferences;

import com.liferay.util.bridges.mvc.MVCPortlet;


public class YoungSikGreetingPortlet extends MVCPortlet {

    @Override

    public void processAction(ActionRequest actionRequest, ActionResponse actionResponse)

        throws IOException, PortletException {

        PortletPreferences prefs = actionRequest.getPreferences();

        String greeting = actionRequest.getParameter("greeting");


        if (greeting != null) {

            prefs.setValue("greeting", greeting);

            prefs.store();

        }


        super.processAction(actionRequest, actionResponse);

    }

}


2) portlet.xml 파일의 내용에서 MVCPortlet을 바꾼다 

<portlet-class>com.mobiconsoft.sample.YoungSikGreetingPortlet</portlet-class>


3) edit.jsp 안에 action을 넣어보자 

   - renderURL : render phase에서만 호출 된다 

   - actionURL : 페이지안의 모든 포틀릿을 rendering 하기전에 action phase를 수행한다

   - resourceURL : xml, images, json, AJAX 요청등

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>

<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %>


<%@ page import="com.liferay.portal.kernel.util.ParamUtil" %>

<%@ page import="com.liferay.portal.kernel.util.Validator" %>

<%@ page import="javax.portlet.PortletPreferences" %>


<portlet:defineObjects />


<%

    PortletPreferences prefs = renderRequest.getPreferences();

    String greeting = (String)prefs.getValue("greeting", "Hello! Welcome to our portal.");

%>


<portlet:actionURL var="editGreetingURL">

    <portlet:param name="mvcPath" value="/edit.jsp" />

</portlet:actionURL>


<aui:form action="<%= editGreetingURL %>" method="post">

        <aui:input label="greeting" name="greeting" type="text" value="<%=

    greeting %>" />

        <aui:button type="submit" />

</aui:form>


<portlet:renderURL var="viewGreetingURL">

        <portlet:param name="mvcPath" value="/view.jsp" />

</portlet:renderURL>


<p><a href="<%= viewGreetingURL %>">&larr; Back</a></p>


4) deploy 해서 확인해 볼 수 있다. 



<참조>

  - 포틀릿 해부하기 

  - 포틀릿 수정하기 

  - 포틀릿 2 Phase Execution

  - pom.xml 파일 샘플 

pom-userxxx-liferay.xml

posted by Peter Note

Liferay는 기본 Ant 기반으로 되어 있고, Maven 기반으로 변경할 수 있다. 



Maven 및 Nexus OSS 설치

  - Ant는 build.xml에 설정하고 Maven은 pom.xml (Project Object Model)에 설정한다 

  - 예전 Spring+Maven 설정 블로깅을 참조한다.

  - 메이븐은 로컬에 Nexus 서버를 설치하여 프로젝트/사내 시스템 단위로 라이브러리를 관리할 수 있다

   + http://www.sonatype.org/nexus/

   + Nexus 설치 가이드

   + 접속 : http://localhost:8081/nexus  (admin / admin123)

   + Optional Install 사항이다. 굳이 설치하지 않아도 됨

  - Liferay Nexus Repository : https://repository.liferay.com/nexus/index.html



Liferay Maven 설정

  - 다운로드 liferay maven 라이브러리 (liferay-portal-maven-[version]-[date].zip) : CE버전만 해당됨

  - 해당 파일에는 Maven artifact에 대한 파일 뿐만 아니라 스크립트 도구도 포함되어 있다. 적절한 위치에 압축을 해제한다 

1) maven은 liferay-portals 소스를 참조하여 빌드되므로 소스를 다운로드 한다 

$ cd ~/development/liferay_portal/sources

$ git clone https://github.com/liferay/liferay-portal.git 


2) app.server.[user name].properties 파일을 liferay-portal 폴더 밑에 생성한다

$ cd ~/development/liferay_portal/sources/liferay-portal

$ vi app.server.[username].properties

app.server.type=tomcat

app.server.parent.dir=/Users/nulpulum/development/liferay_portal/portal-6.2-ce-ga2

app.server.tomcat.dir=${app.server.parent.dir}/tomcat-7.0.42


3) Local Repository 가 아닌 Liferay Central Repository에서 Artifacts 를 설치할 경우 pom.xml에 들어갈 내용

<repositories>

    <repository>

        <id>liferay-ce</id>

        <name>Liferay CE</name>

        <url>https://repository.liferay.com/nexus/content/groups/liferay-ce</url>

        <releases><enabled>true</enabled></releases>

        <snapshots><enabled>true</enabled></snapshots>

    </repository>

</repositories>


<pluginRepositories>

    <pluginRepository>

        <id>liferay-ce</id>

        <url>https://repository.liferay.com/nexus/content/groups/liferay-ce/</url>

        <releases><enabled>true</enabled></releases>

        <snapshots><enabled>true</enabled></snapshots>

    </pluginRepository>

</pluginRepositories>


  > Central Repository in Nexus


 > CLI 명령을 통해 접근 할 수도 있다

mvn archetype:generate -DarchetypeCatalog=https://repository.liferay.com/nexus/content/groups/liferay-ce

   


Liferay Eclipse IDE에 Maven 설정

  - Liferay IDE 2.0 에서는 Maven project configurator (m2e-liferay) 제공

1) 만일 Meven Integration for Eclipse 1.4가 설치되어 있다면 1.5 버전으로 먼저 업데이트한다. 


2) "Help" -> "Install New Software ..." 에서  Liferay SDK와 m2e-liferay 를 삭제하고-already installed 링크 클릭하여 삭제가능- Liferay IDE의 m2e-liferay 두개를 다시 설치한다.  (mobile SDK는 미선택)

Liferay IDE repository - http://releases.liferay.com/tools/ide/latest/stable/.

 


으악 Liferay Perspective가 동작하질 않는다!!!

3) Liferay Eclipse 4.3 - Kepler로 이동하기 

  - 멘붕이 시작되었다. m2e-liferay를 설치한 후 Liferay perspective가 제대로 동작하질 않아서 SDK, m2e 모두 삭제하고 다양한 방법으로 여러번 reinstall 시도를 하여도 Liferay Perspective가 제대로 동작하지 않아서 Eclipse Kepler(4.3) 최신버전으로 Liferay를 재구성 하였다.

  - 다운로드 Eclipse Kepler 설치 

  - JDK v1.7 기반


  - pom.xm 배포위치 & 저장소 위치 & 의존성 관계 라이브러리 환경 설정

    + Global settings, User settings, Parent Project pom.xml, Project pom.xml 4군데 중 하나에 설정한다. 

    + 다른 프로젝트충돌을 방지할려면 자신의 프로젝트 pom.xml에 설정하는게 좋겠다.

    + Global/User settings 에는 profile에 설정하여 pom.xml에서 profile을 불러와서 사용하면 된다. (pom.xml 마다 설정이 필요없음)

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>


    <groupId>com.liferay.sample</groupId>

    <artifactId>sample-parent-project</artifactId>

    <version>1.0-SNAPSHOT</version>

    <packaging>pom</packaging>


    <name>sample-parent-project</name>

    <url>http://www.liferay.com</url>


    <!-- START : Maven 방식 Liferay plugin 생성시 추가해야할 내역 --> 

    <properties>

    <liferay.app.server.deploy.dir>/Users/xxx/development/liferay_portal/www</liferay.app.server.deploy.dir>

    <liferay.app.server.lib.global.dir>

/Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42\lib\ext

    </liferay.app.server.lib.global.dir>

    <liferay.app.server.portal.dir>/Users/xxx/development/liferay_portal/www/ROOT</liferay.app.server.portal.dir>

    <liferay.auto.deploy.dir> /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/deploy</liferay.auto.deploy.dir>

    <liferay.maven.plugin.version>6.2.1</liferay.maven.plugin.version>

    <liferay.version>6.2.1</liferay.version>

    </properties>


    <repositories>

    <repository>

        <id>liferay-ce</id>

        <name>Liferay CE</name>

        <url>https://repository.liferay.com/nexus/content/groups/liferay-ce</url>

        <releases><enabled>true</enabled></releases>

        <snapshots><enabled>true</enabled></snapshots>

    </repository>

</repositories>


<pluginRepositories>

    <pluginRepository>

        <id>liferay-ce</id>

        <url>https://repository.liferay.com/nexus/content/groups/liferay-ce/</url>

        <releases><enabled>true</enabled></releases>

        <snapshots><enabled>true</enabled></snapshots>

    </pluginRepository>

</pluginRepositories>

     <!-- END : Maven 방식 Liferay plugin 생성시 추가해야할 내역 --> 


   <!-- 이하 자동 생성되는 내역 일부임 --> 

    <dependencies>

       <dependency>

         <groupId>com.liferay.portal</groupId>

         <artifactId>portal-client</artifactId>

         <version>${liferay.version}</version>

       </dependency>

       <dependency>

         <groupId>com.liferay.portal</groupId>

         <artifactId>portal-impl</artifactId>

         <version>${liferay.version}</version>

         <scope>provided</scope>

       </dependency>

       <dependency>

         <groupId>com.liferay.portal</groupId>

         <artifactId>portal-pacl</artifactId>

         <version>${liferay.version}</version>

         <scope>provided</scope>

       </dependency>

       <dependency>

         <groupId>com.liferay.portal</groupId>

         <artifactId>portal-service</artifactId>

         <version>${liferay.version}</version>

         <scope>provided</scope>

       </dependency>

       <dependency>

         <groupId>com.liferay.portal</groupId>

         <artifactId>portal-web</artifactId>

         <version>${liferay.version}</version>

         <type>war</type>

         <scope>provided</scope>

       </dependency>

       <dependency>

         <groupId>com.liferay.portal</groupId>

         <artifactId>util-bridges</artifactId>

         <version>${liferay.version}</version>

       </dependency>

       <dependency>

         <groupId>com.liferay.portal</groupId>

         <artifactId>util-java</artifactId>

         <version>${liferay.version}</version>

       </dependency>

       <dependency>

         <groupId>com.liferay.portal</groupId>

         <artifactId>util-slf4j</artifactId>

         <version>${liferay.version}</version>

       </dependency>

       <dependency>

         <groupId>com.liferay.portal</groupId>

         <artifactId>util-taglib</artifactId>

         <version>${liferay.version}</version>

       </dependency>

    </dependencies>

</project>



Maven 방식 Liferay Plugin 생성

  - Liferay는 플로그인 6가지 방식에 대한 Maven의 archetype을 제공하고 있다 

  - Archetype is Maven’s project templating toolkit

1) Eclipse Liferay IDE에서 "New Liferay Plugin Project" 선택

2) Build type을 Maven으로 선택

다음에서 MVCPortlet 선택


* CLI 명령 생성하기 

- 생성

mvn archetype:generate -DarchetypeCatalog=https://repository.liferay.com/nexus/content/groups/liferay-ce


- 배포 

mvn liferay:deploy


3) IDE로 생성된 pom.xml 에는 오류가 있다. 위의 pom.xml에서 추가해야 할 것들을 추가한다

  - properties : 배포 위치 및 버전 정보

  - repositories : Liferay remote repository 위치 (Nexus OSS로 local proxy server 사용하지 않을 경우)

  - pluginRepositores 


4) Eclipse에서 "mobiconsoft-sample"을 선택하고 컨텍스트메뉴 "Liferay" -> "Maven" -> "liferay:deploy" 를 선택하여 war파일을 만들어 본다 

[INFO] --- liferay-maven-plugin:6.2.1:deploy (default-cli) @ mobiconsoft-sample ---

[INFO] Deploying mobiconsoft-sample-1.0.0-SNAPSHOT.war to /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/deploy

     [null] Copying 1 file to /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/deploy

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------


deploy 디렉토리에 생성된 모습


5) Eclipse에서 해당 war를 tomcat 컨텍스트로 추가하고 시작하면 Liferay 화면에서 확인을 할 수 있다


Liferay v6.2 CE Server를 시작하면 Liferay 화면이 뜬다 


  - 그외 Maven 아키타입들 

liferay:portlet, liferay:hook, liferay:ext, liferay:theme, liferay:layout


그외 ...

Liferay ServiceBuilder portlets

Liferay webs

Liferay Ext

JSF Portlet Archetype

ICEFaces Portlet Archetype

PrimeFaces Portlet Archetype

Liferay Faces Alloy Portlet Archetype

Liferay Rich Faces Portlet Archetype

DBBuilder - The build-db goal lets you execute the DBBuilder to generate SQL files.

SassToCSSBuilder - The build-css goal precompiles SASS in your css; this goal has been added to theme archetype.



지금까지 환경설정을 하고 어떻게 Maven으로 빌드하는 과정을 보았다. 다음 블로깅에서는 플로그인 개발 실무를 보도록 한다. 



<참조>

  - Liferay Maven 빌드 환경 구성하기

  - 샘플 pom.xml 파일 

pom-userxxx-liferay.xml

posted by Peter Note

Liferay의 Service Builder 도구는 비즈니스 업무를 만들어내는 도구이다. 그리고 Plugins SDK에 대해서 알아보도록 한다. 



Service Builder 개념  

  - Service Builder 도구는 Model-Driven code generator이다. 

  - WEB-INF/service.xml 안에 entity로 정의되고 이를 참조하여 model, persistence, service 파일이 자동 생성되는 구조이다. 

  - 자동 생성된 내역은 여러다른 포틀릿에서도 공유할 수 있게 구성된다 

 > service.xml 에서 Entity를 추가하고 필드를 정의 할 수 있다


> 각 Entity에 대해서 Column을 정의한다 



> Event와 Location을 추가했는데 두 entity간의 relation을 Diagram에서 맺어 줄 수 있다


  - event-listing 프로젝트를 선택하고 컨텍스트메뉴를 보면 "Liferay" -> "Build Services" 를 선택하여 파일을 자동 생성한다

> WEB-INF/src , sql 밑으로 소스가 자동 생성되었다 



기존에 있는 프로젝트 Import하여 분석하기 

  - "New" -> "New Liferay Project form Existing Source" 로 기존의 플러그인을 Eclipse에 불러서 사용할 수 있다 

  - Plugins SDK 안에 존재하는 플러그인을 불러오고 싶다면 "Import..." -> "Liferay Projects from Plugins SDK"를 선택한다 

> Liferay에서 선택


> SDK를 선택하면 Plugins SDK에 포함된 플러그인을 자동 열거하여 준다. 원하는 것을 선택하고 Runtime을 선택한다 



Plugins SDK 이해하기

  - Plugin SDK 설치는 지난 블로그에서 다루었다. 이제 Plugins SDK의 환경설정은 build.properties에서 한다

> 환경설정 주의

 ~/development/liferay_portal/plugins-sdk-6.2/build.properties 는 절대로 변경하지 말자

 대시 build.<username>.properties 파일을 변경한다. 

  - SDK 디렉토리 구조

clients/ - client applications directory.

dist/ - archived plugins for distribution and deployment.

ext/ - Ext plugins directory. See Advanced Customization with Ext Plugins.

hooks/ - hook plugins directory. See Customizing and Extending Functionality with Hooks.

layouttpl/ - layout templates directory. See Creating Liferay Layout Templates.

lib/ - commonly referenced libraries.

misc/ - development configuration files. Example, a source code formatting specification file.

portlets/ - portlet plugins directory. See Developing Portlet Applications.

themes/ - themes plugins directory. See Creating Liferay Themes and Layout Templates.

tools/ - plugin templates and utilities.

webs/ - web plugins directory.

build.properties - default SDK properties.

build.<username>.properties - (optional) override SDK properties.

build.xml - contains targets to invoke in the SDK.

build-common.xml - contains common targets and properties referenced throughout the SDK.

build-common-plugin.xml - contains common targets and properties referenced by each plugin.

build-common-plugins.xml - contains common targets and properties referenced by each plugin type.



Ant 기반 빌드하기 

  - build.xml은 Ant 빌드 파일이다 

build-service - builds the service layer for a plugin, using Liferay Service Builder.

clean - cleans the residual files created by the invocations of the compilation, archiving, and deployment targets.

compile - compiles the plugin source code.

deploy - builds and deploys the plugin to your application server.

format-source - formats the source code per Liferay’s source code guidelines, informing you of violations that must be addressed. See the Development Sytle community wiki page for details.

format-javadoc - formats the Javadoc per Liferay’s Javadoc guidelines. See the Javadoc Guidelines community wiki page for details.

  - CLI (Command Line Interface) 기반으로 포틀릿 만들기 

> SDK/portlets/ 안에 보면 create.bat/create.sh 파일이 존재한다 

$ cd ~/development/liferay_portal/plugins-sdk-6.2/portlets

$ ./create.sh test-listing "test listing"


> 해당 디렉토리에서 배포하기 

$ ant deploy 


> 배포에 성공하게 되면 하기 디렉토리에 war파일 두개(event-listing-portlet, test-listing-portlet)이 생성된다 

  $PORTAL_HOME/deploy/*.war 파일은 tomcat에 배포된다 


다음 블로그에서는 Maven 기반 빌드를 보도록 하자. 



<참조>

  - Plugins SDK 이해

posted by Peter Note
2014. 4. 30. 17:28 Study Frameworks/Liferay Portal

Liferay 포털의 개발은 결국 기존 플러그인들의 확장 및 신규 개발 관건이다. 툴에 대한 이해를 하고 설치 사용해 보자 



Liferay IDE

  - Eclipse 플러그인으로 Eclipse Market에서 검색하여 설치가능 : "liferay ide"

  - Plugin SDK 또는 Maven과 Liferay runtime 환경과 결합하여 개발한 것을 배포 테스트 가능케 해준다  

  - Eclipse Indigo(3.7.x), Juno(4.2.x), Kepler(4.3.x) 에서 설치가능 

  - Plugin SDK와 Runtime 환경 설정 블로깅 참조 



Liferay Project 생성 

  - Eclipse의 Liferay IDE Perspective를 띄운다 

  - "New" -> "Liferay Plugin Project"를 선택한 후 하기와 같이 입력하고 "Service Builder"를 선택하고 생성한다 

    

  - 프로젝트 생성 내역

> docroot : 실제로 서비스하는 영역이다 

> view.jsp : 포틀릿으로 보여지는 웹 화면

> *xml : 포틀릿 설정 화면 


- liferay-display.xml 

  포틀릿 카테고리로 sample에 해당 포틀릿이 들어간다 

<display>

  <category name="category.sample">

    <portlet id="event-listing" />

  </category>

</display> 


  - 포틀릿 배포하기 

> 포틀릿을 생성하면 위치는 sdk/portlets 폴더밑으로 생성된다 


> Eclipse에서 Tomcat Server를 등록하여 기동한 상태라면 "And and New.." 컨텍스 메뉴를 선택하여 event-listing-porlet을 Add 하면 자동으로 Hot Deploy이 되어 반영된다 


- 포탈 화면에서 확인해 보자 

Sample 밑의 카테고리에 "Event Listing" 포틀릿이 존재하여 드래그앤드롭하여 원하는 위치에 놓을 수 있다


  - 프로젝트를 만든 후 기본 제공하는 항목을 제거하고 플러그인을 만들것이다. 기본 제공 설정내역을 삭제해 보자

1) WEB-INF/liferay-display.xml  에서 <portlet>..</portlet> 부분 제거 

2) WEB-INF/liferay-portlet.xml 에서  <portlet>..</portlet> 부분 제거 

3) WEB-INF/portlet.xml 에서  <portlet>..</portlet> 부분 제거  

4) view.jsp 파일 제거 


삭제까지 진행되었으면 이제 해당 프로젝트에 새로운 플러그인을 만들 준비가 되었다. 



Liferay Plugin 개발 

  - 프로젝트에는 여러개의 플러그인이 포함될 수 있다. 위에 만들어 놓은 "Event Listing" 프로젝트안에 여러개의 플러그인을 넣어본다 

  - Location Listing과 Event Listing 포틀릿 플러그인을 만들어 보자 

  - Location Listing Portlet 생성

1) 프로젝트명을 선택하고 context menu -> New -> "Liferay Portlet"을 선택하고 LocationListing 포틀릿을 입력


2) Next로 이동하여 포틀릿 정보를 입력


3) 카테고리 정보를 입력한다 "MobiconSoft"


  - event-listing 포틀릿을 생성한다 

> location-listing 과 같은 방식으로 생성

  MVCPortlet을 상속 받으면서 같은 java package에 속한다 


> 최종 생성된 Eclipse Project 모습

   + com.mobiconsoft.event의 클래스들은 MVCPortlet을 상속받아서 자동 생성된다 

   + liferay-display.xml, liferay-portlet.xml, portlet.xml 안에 위저드로 설정한 내용이 반영된다 


  - 프로젝트의 컨텍스트가 이미 Server에 반영되어서 위저드로 생성시에 Hot Deploy가 된다. 다음으로 "Mobiconsoft" 카테고리에 EventListing과 LocationListing이 반영되었는지 확인한다. 




다음 블로그에서는 "Service Builder" 도구를 사용하여 어떻게 Model, Persistence, Service Layer를 만들 수 있는지 살펴보도록 한다 


posted by Peter Note
2014. 4. 30. 17:20 Study Frameworks/Liferay Portal

기본 설치할 때 기본데이터를 체크하였다면 "Joe Bloggs" 가 나오고 몇가지 직관적인 메뉴를 통해서 포털의 화면을 구성할 수 있다. 가장 기본적인 것이라서 개발자 입장에서 기존에 제공하는 것의 변경 및 필요한 기능의 개발은 어떻게 하는지 Liferay Developer Guide (v6.2 기준) 를 통해 보도록 하자. 



가이드 내역 

  - Developing Application for Liferay

  - Extending and Customizing Liferay

  - 개발툴 선택하기 



Liferay를 위해 어플리케이션 개발 

  - liferay에서 어플리케이션을 연동하는 두가지 방법

    + Portlets 

    + OpenSocial gadget : 

  - Portlets

    + 자바로 쓰여진 어플리케이션 모듈로 포틀릿 컨테이너안에서 구동됨 

    + liferay 서버에 hot deploy가 가능하여 서버 재기동이 필요없음

    + 하나의 플러그인에 여러개의 포틀릿을 두어서 화면을 구성할 수 있다 

    + Liferay에서 정의한 프레임워크 : MVC 포틀릿, Alloy 포틀릿으로 구현된다 

  - OpenSocial gadget

    + 여러 언어로 개발가능

    + remote / local gadget 으로 구분

  - AlloyUI

    + v6 부터 클라이언트에서 사용

    + YUI3 이용

    + 다른 자바스크립 라이브러리 추가 가능



Liferay 기능 확장 및 커스터마이징

  - 여러 프러그인을 하나로 묶은 WAR로 배포하여 사용할 수 있다

  - Theme 사용자 정의 

    + Look and Feel에 대한 제어가 가능

    + Velocity 또는 FreeMarker와 CSS를 혼용하여 사용

    + 업무로직과 별개의 RWD(Responsive Web Desgin)이 가능

    + AlloyUI를 사용

  - Layout Templates

    + theme과 유사하지만 look and feel이 아니라 화면 페이지에 포틀릿 배치 정의에 대한 것이다 

    + Velocity를 사용하여 hot deploy 된다 

  - Hook Plugin

    + Liferay의 core function에 대하여 hooking 처리 할 수 있는 기능

    + 로그인, 세션관리, 코어 서비스들

  - Ext Plugin

    + Liferay core를 큰규모의 변경을 가하고자 할 때 사용

    + 친숙하고 정말 필요할 때만 사용. 서버 재기동 필요

 


개발 툴 선택 

  - CLI 방식 : ANT + Plugin SDK 

  - GUI 방식 : Eclipse 기반 Liferay IDE + Plugin SDK

  - Maven을 사용할 수 있다 

 

다음 단락에서는 3가지를 통해 개발하는 방법을 알아본다 

 > Developing Apps with Eclipse Liferay IDE

 > Leveraging the Plugins SDK

 > Developing Plugins Using Maven



<참조>

  - Liferay Developer Guide

  - Ext Plugin 개발 가이드

posted by Peter Note
2014. 4. 30. 12:33 Study Frameworks/Liferay Portal

Liferay CE v6.2 GA2 버전을 다운로드 받고 설치하는 과정과 Eclispse ID와 Liferay Plugins SDK도 설치해 보자. 



Liferay 다운로드 

  - JDK : v7.* 버전을 권장하고 없다면 V6.* 도 가능하다

  - 다운로드 파일 :  the liferay + tomcat bundle

  - 다운로드후 적당한 위치에 zip 압축을 푼다. (linux 기준)

// 압축을 풀었을 때 폴더명칭을 portal-6.2-ce-ga2 로 변경하였다 (사용자 정의함)

~/development/liferay_portal/portal-6.2-ce-ga2


  - data : lucene 데이터 파일

  - deploy : 플러그인 배포 .war 파일 놓는곳

  - tomact : 톰캣 위치 


// portal-ide.properties

auto.deploy.tomcat.conf.dir=/Users/xxxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/conf/Catalina/localhost


// portal-setup-wizard.properties 및 liferay와 mysql 연결 정보 (username : liferay, database : liferaydb)

liferay.home=/Users/xxxx/development/liferay_portal/portal-6.2-ce-ga2
admin.email.from.address=test@liferay.com
jdbc.default.driverClassName=com.mysql.jdbc.Driver
jdbc.default.username=liferay
jdbc.default.url=jdbc:mysql://localhost:3306/liferaydb?autoReconnect=true



Liferay Tomcat 설정

  - tomcat 위치

~/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42

  - 디폴트 웹 어플리케이션 위치

~/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/webapps/ROOT

  - 웹 어플리케이션 위치를 옮길 경우 tomcat의 환경설정

// 변경위치를 www 로 할 경우

~/development/liferay_portal/portal-6.2-ce-ga2/www


// ~/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/conf/server.xml 수정

<Host appBase="/Users/xxxx/development/liferay_portal/www" autoDeploy="true" name="localhost" unpackWARs="true">

    <Context docBase="ROOT" path="" reloadable="true"/>

    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t &quot;%r&quot; %s %b" prefix="localhost_access_log." suffix=".txt"/>

</Host>

  - tomcat 시작

~/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/bin/startup.sh


몇분을 기다리면 브라우져가 자동 시작하여 http://localhost:8080/ 을 호출하고 초기 셋업페이지가 나온다. 

이때 mysql 연결로 바꾸자 (portal-setup-wizard.properties 참조)



Liferay Plugins SDK 설치 

  - 플러그인 소스 다운로드받자 : 나중에 eclipse ide에서 debugging 하거나 수정할 때 사용

$ cd  ~/development/liferay_portal/

$ mkdir sources && cd sources


// 플러그인 소스 다운로드 

$ git clone https://github.com/liferay/liferay-plugins.git

  - 다운로드 Liferay CE v6.2 ga2 Plugins SDK   

// ~/development/liferay_portal/ 위치에 sdk 압축을 푼다 

~/development/liferay_portal/plugins-sdk-6.2


// 최종 디렉토리 구조

  - ~/development/liferay_portal/plugins-sdk-6.2/build.<사용자명>.properties 내용 수정

app.server.tomcat.lib.global.dir = /Users/nulpulum/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/lib/ext

#app.server.tomcat.deploy.dir = /Users/nulpulum/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/webapps

app.server.tomcat.deploy.dir = /Users/nulpulum/development/liferay_portal/www

app.server.parent.dir = /Users/nulpulum/development/liferay_portal/portal-6.2-ce-ga2

app.server.tomcat.dir = /Users/nulpulum/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42

app.server.type = tomcat

#app.server.tomcat.portal.dir = /Users/nulpulum/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/webapps/ROOT

app.server.tomcat.portal.dir = /Users/nulpulum/development/liferay_portal/www/ROOT



Eclipse IDE 설치

  - Eclipse Juno 버전 이상에서 "Eclipse Marketplace..." 에 들어가서 "liferay" 검색하면 "liferay IDE" 이클립스 플러그인이 나오면 Install

    


  - Liferay perpective를 띄웠을 때 화면 

    툴바에서 파란색 아이콘 3가지가 Liferay 기능이다. 

    


  - Liferay의 tomcat 서버를 등록한다 

    


  - 다음으로 Plugins SDK를 등록한다 

    



*** Liferay IDE를 통한 개발 (주의사항)

  - 기본 {TOMCAT_HOME}/webapps/ROOT 를 사용한다. 

  - 기존 ~/liferay_portal/www 에서 다시 원위치로 설정한다.

   (단, ant CLI 방식은 괜찮다)

  - 사용자가 xxx 일 경우

1) {TOMCAT_HOME}/conf.xml 에서 위치 변경

<Host appBase="/Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/webapps" autoDeploy="true" name="localhost" unpackWARs="true">


2) {PLUGIN_SDK}/build.<username>.properties

app.server.tomcat.lib.global.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/lib/ext

app.server.tomcat.deploy.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/webapps

app.server.parent.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2

app.server.tomcat.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42

app.server.type = tomcat

app.server.tomcat.portal.dir = /Users/xxx/development/liferay_portal/portal-6.2-ce-ga2/tomcat-7.0.42/webapps/ROOT



<참조>

  - Liferay 환경 구성 - 한글

  - Liferay 초기 환경 설정

  - Liferay 개발환경 구성

  - Liferay tomcat 시작하기

  - A to Z Liferay 개발환경 설정하기 (필독)

posted by Peter Note
2014. 4. 30. 12:32 Study Frameworks/Liferay Portal

Liferay는 Enterprise Portal을 만들기 위한 솔루션이다. 커뮤니티 버전(CE)과 엔터프라이즈 버전(EE)이 존재하고, 오픈소스버전은 CE버전을 사용하면 된다. Liferay를 들어가기전에 Portal에 대한 올바른 이해와 설치 및 개발환경 설정 그리고 플로그인 개발을 보고 요즘 가장 핫한 Angular.js와 Node.js를 접목하여 클라이언트단 포틀랫(Portlet) 개발생산성 향상과 Node.js의 Socket.io를 이용한 실시간(Realtime) 구현에 대해서도 연재를 통하여 알아보자.





엔터프라이즈 포탈(EP)이란 무엇인가?

기업이 전술상 또는 전략상으로 필요로 하는 업무용 어플리케이션을 적은 비용으로 빠르게 구현하여 비즈니스 유저들에게 제공함으로써 IT는 업무 어플리케이션 구축 비용 및 시간을 절약하고 비즈니스 유저들은 자신의 업무에 필요한 어플리케이션을 신속히 제공받음으로써 사업의 기회비용을 줄일 수 있도록 지원하는 시스템. -인용-


  - 보다 자세한 이해는 엔터프라이즈 포탈이란 무엇인가? 연재글을 참조한다 

  - EP를 위하여 통합적으로 운영되어야 할 부분

    + 통합 검색 : 문서 검색 뿐만아니라 서비스/애플리케이션 단위의 검색도 가능

    + 콘텐츠 관리 : 디지털 컨텐츠/미디어의 Publish/Delivery 가능

    + 협업 기능 : SNS, 메신저, 게시판과 같은 온라인 소셜활동 가능 

    + 기타 : 다양한 어플리케이션/시스템 통합 프레임워크 제공 (SSO, KMS, BPM, ESB, SNS 등의 연동)



Liferay Portal이 제공하는 기능

  - 메일 : SMS Text Messenger

  - 블로그 : Blog, Asset Publisher (파일공유), RSS, Twitter

  - 위키 : Wiki

  - 문서 관리 : Document Library 문서공유 

  - 일정 관리 : Calendar

  - 게시판 : Message Boards (게시판) 

  - 투표 : Polls

  - 이미지 관리 : Image Gallery

  - 기사 : Journal, News

  - 공지 : Announcements, Alerts



Liferay 기술 스택

  - 서버 : Java(JDK v7 권장), Spring, Hibernate, EJB(optional)

  - 클라이언트 : AlloyUI (YUI3 + Twitter Bootstrap)



Liferay 버전 및 파일 다운로드

  - CE v6.2 GA2 (2014.4월 현재)

  - Portal 과 Plugins 파일로 구성됨

    + Portal은 메인 어플리케이션

    + Plugin은 확장 모듈이며 6가지(theme, portlet, EXT, layout template, hooks, web modules) 타입이 존재

  - GitHub : https://github.com/liferay/



다음글에서는 Liferay를 설치하고 설정하는 방법에 대해 알아보자 



참조

  - 엔터프라이즈 포탈이란 무엇인가?

  - Liferay GitHub Repository

posted by Peter Note
2014. 4. 25. 00:15 NodeJS/Concept

Node.js를 제대로 배워보고 싶다면 이렇게 시작해보자


1. Node.js 사용 영역

  - fast, real-time 어플리케이션 개발

  - scalable 확장가능성이 높은 어플리케이션 개발

  - data-driven modern web 어플리케이션 개발

  * 클라우드 환경 이해



2. 잘 못 된 코스 

  - 온라인 및 동영상 강좌들부터 보지 말기

  - 서점에서 서평들 읽고 책 고르지 말기 



3. 제대로 된 코스

  - 봐야할 리소스 

    + 초보 : The Node Beginner Book 번역본

    + Felix's NodeJS Guide 번역본

   + Node.js 소개 자료 -여름으로 가는 문

   + Mastering NodeJS (비젼미디어)


  - JavaScript 를 배운다 : JavaScript Graden 번역본


  - 설치 : Professional Node.js 1장 보고 개발환경 셋업

  - 이해 : Beginner Book 보고서 간단히 프로그램들 돌려보기 

  - 모듈 : CommonJS에 대하여 이해하기

  - Professional Node.js 3장부터 쭉 읽자. 그러면 서버단 개발에 대해서 이해할 수 있다

  - 우리 목적은 modern web application개발이니 front-end의 backbone.js 를 배운다 



4. Node.js & Backbone.js를 익혔다면 

  - http://dailyjs.com/web-app.html 여기서 다양한 어플리케이션의 모험을 해보시라

  - 더 나아가서는 템플릿을 위한 Handlebars.js 와 MongoDB를 익힌다


  * 2013.2.20 일에 작성한 블로그라서 backbone.js가 나왔다. 이제는 angular.js로 옮겨가는 분위기

    3주를 목표로 잡고 시작하시라~~~ Good Luck!



<참조>

  - 원문 : http://javascriptissexy.com/learn-node-js-completely-and-with-confidence/

posted by Peter Note
2014. 2. 4. 10:43 AngularJS/Start MEAN Stack

메뉴의 우측에 로그인을 생성하고 로그인 화면을 트위터 부트스트랩을 이용하여 만들고 로그인 처리를 한다. 


사전 준비 

  트위터 부트스트랩을 이용하여 먼저 화면 프로토 타입을 만든다. 그리고 앵귤러 Controller를 코딩한 후 서버와 통신을 위한 앵귤러 Service를 만들자. 그리고 필요에 따라 Framework 요소로 필요한 것을 만들어 간다. 하나의 업무처리를 위하여 HTML+Controller+Service 를 기억억한다.  


  - 비즈니스 HTML View 작성

  - 비즈니스 Controller 코딩

  - 비즈니스 Service 코딩



1. 로그인 메뉴 추가하기

  메뉴는 menu.html 로 별도 구성하였고, 맨 하단에 로그인이 안되었을 때와 로그인이 되었을 때를 나타내는 <li> 태그를 넣어준다. currentUser는 로그인 성공후 $rootScope에 저장되는 사용자 객체이다. <li> 태그는 ng-hide/ng-show에의해 보여졌다 안보여졌다 한다.

     ... 중략 ...

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


    <ul class="nav navbar-nav navbar-right">

      <li ng-hide="false">

        <a href="#" ui-sref="login">Login</a>

      </li>

      <li ng-show="false">

        <a href="#" ng-click="logout()">

          [{{ currentUser.name }}] Logout

        </a>

      </li>

    </ul>


  </div>

</div>



2. 로그인 화면 구성

  ng-hide="false" 이므로 현 상태에서는 "Login" 메뉴가 나오고, 메뉴 클릭시 ui-sref="login" 에 따라 views/login.html 화면으로 이동을 한다.  Bootsnipp에서 login 을  (http://bootsnipp.com/search?q=login) 검색하면 다양한 로그인 창 디자인이 나오므로 활용해 본다.


  - 로그인을 할 때 컨트롤러에서 처리할 메서드를 정의한다 

    submitLogin()

  - 정보 객체를 정의한다  

    login = { email: 'xxx', password: 'xxx' }

  - type 과 required 지정을 통해 필수항목 유효성 체크를 한다 

<div class="row">

  <div class="col-md-4 col-md-offset-4">

    <h3 class="text-center">LOGIN</h3>

    <form name="loginForm" ng-submit="submitLogin()">

      <div class="form-group">

        <div class="input-group">

          <span class="input-group-addon">

            <i class="fa fa-envelope-o"></i>

          </span>

          <input id="login_email" type="email" name="email" class="form-control input-lg" placeholder="Email" ng-model="login.email" required>

        </div>

      </div>


      <div class="form-group">

        <div class="input-group">

          <span class="input-group-addon">

            <i class="fa fa-asterisk"></i>

          </span>

          <input id="login_password" type="password" name="password" class="form-control input-lg" placeholder="Password" ng-model="login.password" required>

        </div>

      </div>


      <button type="submit" class="btn btn-primary btn-lg btn-block btn-shadow">Login</button>

    </form>

  </div>

</div>


  우측 상단의 "Login" 메뉴 클릭시 출력되는 최종화면이다 



3. 로그인 앵귤러 Controller 개발 

  로그인 화면에 대한 처리를 하는 컨트롤러는 LoginCtrl (app/scripts/controllers/login.js) 이다. 앵귤러의 컨트롤러는 뷰와 모델 객체를 주고 받으며 처리하는 역할만을 담당하도록 만든다. 즉, 서버와 연결하고 처리하는 것은 앵귤러 서비스를 만들고 주입(DI)을 받아 사용한다. 로그인이 성공했다고 가정하여 UI-Router의 $state를 이용하여 main 화면으로 이동하는 코드를 넣었다. 

 'use strict';


angular.module('meanstackApp')

  .controller('LoginCtrl', [

  '$scope', 

  '$state', 

  function ($scope, $state) {

   $scope.submitLogin = function() {

    console.log($scope.login);

     $state.go("main");

   }

  }]);



로그인 앵귤러 Service 개발 

  로그인 정보를 받아서 서버와 통신하는 모듈을 만들도록 한다. Node.js와 RESTful 방식으로 요청을 처리한다. 앵귤러에서는 HTTP 요청 처리를 위하여 $http와 이를 보다 추상화한 $resource를 제공한다. 하지만 클라이언트와 서버사이의 통신을 위하여 몇가지 공통모듈을 만들 필요가 있다. 


  - 서버와 주고받는 요청/응답을 추상화한 객체 

  - $resource를 추상화한 서비스   



1. 서버 응답/요청 객체 추상화 

  자바의 J2EE 스펙중 Servlet 스펙을 보면 요청/응답을 처리하는 서블릿은 HTTPServletResponse/HTTPServletRequest가 있다. 이와 유사하게 앵귤러와 Node.js에 사이에 주고 받는 JSON 객체를 추상화 한다. MS를 MEANStack의 약어로 공통모듈의 경우 앞에 붙여 사용한다. 요청객체는 앵귤러 Factory 로 개발한다.  


  - 요청객체 : MSRequest 를 통해 정형화된 형태의 JSON 객체를 생성한다 

  - 응답객체 : MSResponse 는 서버로 부터 전달되는 정형화된 형태의 JSON 객체이다 


  RESTful API를 만들기 위한 몇가지 원칙을 지키고 그외에는 변형하여 사용토록 한다. URI 정의시 몇가지 원칙만을 가지고 진행토록한다. 복잡하게 하지 말고 Stateless하면서 Resource 접근에 대해서 명사를 사용하며 동사적 의미는 HTTP POST, GET, PUT, DELETE를 사용하며, Collection의 경우는 복수를 사용한다. 즉 더 복잡한 부분이 나오면 그때 가서 확장하거나 별도 구성하여 사용하고 몇가지 원칙을 정해서 사용토록 한다 


  - prefix 로 버전을 반드시 붙인다 : api/v1

  - 3단계의 정의를 한다 : /:area/:resource/:id 

    + area : authentication, person, tech, guru 등의 resource 영역 구분

    + resource : login, mylevel(myfollowing), angularjs(nodejs, expressjs, mongodb), month(member)같은 좀 더 구체적인 구분

    + id : GET, PUT, DELETE의 경우 옵션적으로 사용

    + 그외의 데이터는 Key=Value 파라미터 값으로 넘긴다 


// MSRequest 생성 및 개발 

$ yo angular:factory ms-factory

   create app/scripts/services/ms-factory.js

   create test/spec/services/ms-factory.js


// ms-factory.js

angular.module('meanstackApp')

  .factory('msRequestFactory', function () {

  

  var createRequest = function(area, resource, id, request) {

    if (!request)

      request = {};


    return {

      "area" : area,

      "resource" : resource,

      "id" : id,

      "request" : request

    };

  };


  return {

    createRequest : createRequest

  };


});


// 공통 호출 팩토리 생성

$ yo angular:factory ms-restful-api

   create app/scripts/services/ms-restful-api.js

   create test/spec/services/ms-restful-api.js


// ms-restful-api.js 

angular.module('meanstackApp')

  .factory('msRestfulApi', ['$resource', function ($resource) {

  var prefixUrl = '/api/v1';


  return $resource(

      prefixUrl + '/:area/:resource/:id', 

     {

       area : "@area",

       resource : "@resource",

       id : "@id"

     },

     {

      'get':    {method:'GET', isArray:true},

      'save':   {method:'POST'},

      'update': {method:'PUT'},

      'delete': {method:'DELETE'},

      'login':  {method:'POST'}

    });

  }]);


  $resource의 파라미터는 첫번째 URI 두번째 URI의 구분자에 맵핑되는 JSON, 세번째는 사용자 정의 메소드이다. 



2. 로그인 Service 개발 

  컨트롤에서 로그인 관련 서버호출을 위하여 MSRequest 팩토리와 RESTful하게 호출할 수 있는 팩토리를 만들었다. 이를 활용하는 앵귤러 Service 코딩해 보자. 


  - 사용자 로그인을 처리하는 서비스를 만든다.

  - 서비스는 View에서 Controller를 통하여 로그인 정보(params)를 받아서 Service로 넘긴다.

  - 성공적으로 완료되면 Controller에서 넘겨준 펑션(successCallback)을 수행한다. 


Service는 순수하게 서버와의 요청 처리만을 담당하고 Controller는 HTML View에서 넘어온 Model 값의 핸들링과 이후 Service에서 성공을 하면 실행할 successCallback을 갖는다. successCallback은 UI Routing 또는 View의 변경을 수행한다. Service와 Controller 서로의 역할을 정확히 나눔으로써 유지보수성을 높이도록 하였다. 


// 로그인 관련 서비스를 만든다 

$ yo angular:service session-service

   create app/scripts/services/session-service.js

   create test/spec/services/session-service.js


// session-service.js

angular.module('meanstackApp')

  .service('SessionService', [

  'msRequestFactory', 

  'msRestfulApi', 

  '$log',

  function SessionService(msRequestFactory, msRestfulApi, $log) {

    

    this.login = function(paramssuccessCallback) {

    // 1) create request

    var request = msRequestFactory.createRequest('authentication', 'login', '');

   

    // 2) set params

    request.params = {

    email: params.email,

    password: params.password

    }


    // 3) call ajax 

    msRestfulApi.login(request, 

    function(response) {

    // success

    successCallback(response);

    }, 

    function(error){

    // fail

    $log.error('Server Exception is ', error);

    })

  };


  }]);


  사용자가 로그인을 한후 사용자 정보를 담는 SessionInfo 객체를 관리하는 서비스를 만들어 보자. 

  - 사용자 정보 저장소로 HTML5의 localStorage를 우선 사용한다 

  - 현재 사용자 정보를 $rootScope.currentUser에 담아서 애플리케이션 전체 영역에서 사용토록 한다 

// 세션 정보 생성 

$ yo angular:service session-info

   create app/scripts/services/session-info.js

   create test/spec/services/session-info.js


// session-info.js

angular.module('meanstackApp')

  .service('SessionInfo', ['$rootScope', function SessionInfo($rootScope) {

    this.localStorageKey = "__SESSION_INFO";

    try {

      $rootScope.currentUser = JSON.parse(localStorage.getItem(this.localStorageKey) || "{}");

    } catch(e) {

      $rootScope.currentUser = {};

    }


    this.getCurrentUser = function() {

      return $rootScope.currentUser;

    }


    this.isUserSignedIn = function() {

      if(this.getCurrentUser() && this.getCurrentUser().id) {

        return true;

      } else {

        return false;

      }

    };


    this.setUserInfo = function(info) {

      angular.extend($rootScope.currentUser, info);

      localStorage.setItem(this.localStorageKey, JSON.stringify($rootScope.currentUser));

    };


    this.reset = function() {

      $rootScope.currentUser = {};

      localStorage.setItem(this.localStorageKey, JSON.stringify($rootScope.currentUser));

    };

  }]);



  SessionService에서 로그인 요청이 성공하면 SessionInfo를 호출하는 코드를 넣도록 한다. 

// SessionInfo를 사용하는 SessionService 수정 

angular.module('meanstackApp')

  .service('SessionService', [

   'msRequestFactory', 

   'msRestfulApi', 

   'SessionInfo',

   '$log',

   function SessionService(msRequestFactory, msRestfulApi, SessionInfo, $log) {

    

    this.login = function(params, successCallback) {

     // 1) create request

     var request = msRequestFactory.createRequest('authentication', 'login', '');

    

     // 2) set params

     request.params = {

     email: params.email,

     password: params.password

     }


     // 3) call ajax 

     msRestfulApi.login(request, 

     function(response) {

       // success

        SessionInfo.reset();

                   SessionInfo.setUserInfo(response);

        successCallback(response);

     }, 

     function(error){

       // fail

                  alert('Login Failed');

       $log.error('Server Exception is ', error);

     })

   };


  }]);



3. 로그인 Controller 개발

  이제 앵귤러 Service가 만들어 졌으니 Service를 사용하는 Controller를 코딩해 보자. 로그인 Service 가 성공하면 메인화면으로 이동한다.  

// login.js 의 기존 코드 

angular.module('meanstackApp')

  .controller('LoginCtrl', [

  '$scope', 

  '$state', 

  function ($scope, $state) {

   $scope.submitLogin = function() {

    console.log($scope.login);

     $state.go("main");

   }

  }]);


// SessionService를 사용하는 수정 코드 

angular.module('meanstackApp')

  .controller('LoginCtrl', [

  '$scope', 

  '$state', 

  'SessionService',

  'SessionInfo',

  '$log',

  function ($scope, $state, SessionService, SessionInfo, $log) {


   $scope.submitLogin = function() {

    SessionService.login($scope.login, changeState);

   }


   function changeState(response){

    alert(SessionInfo.getCurrentUser().name + '님 반갑습니다 :)');

        $state.go("main");

   }

  }]);


최종적으로 Chrome 개발자 도구의 console에 아무런 에러가 없으면 모든 서비스와 컨트롤러가 앵귤러 초기화가 잘 되었다는 신호이다. 로그인을 해보면 아마도 404에러가 날 것이다. 아직 서버가 준비되지 않았기 때문인데, 이는 다음장에서 테스트 코드를 작성해서 확인해 보록한다. 다음과 같인 나오면 성공이다. 



4. 로그인 html 수정

  SessionInfo에는 사용자가 로그인 상태인지 체크하는 메소드가 존재한다. 이를 menu.html 에 적용하자 

<ul class="nav navbar-nav navbar-right">

<li ng-hide="SessionInfo.isUserSignedIn()">

<a href="#" ui-sref="login">Login</a>

</li>

<li ng-show="SessionInfo.isUserSignedIn()">

<a href="#" ng-click="logout()">

[{{ currentUser.name }}] Logout

</a>

</li>

</ul>


위의 코드는 해당 브랜치에 존재한다. 

https://github.com/MEAN-STACK/MEANStack-Bookwork/tree/angular_step03_login-ctrl-service

posted by Peter Note
2014. 1. 30. 11:15 AngularJS/Start MEAN Stack

UI-Router를 이용한 화면 라우팅 설정을 app/scripts/app.js 메인 애플리케이션 파일에 설정하였다. 설정 내용중 각 라우팅되는 HTML View의 제어를 담당하는 Controller를 yeoman을 사용해서 생성해 보자 



HTML과 Controller 

  앵귤러는 MVW 프레임워크라고 하는데 여기서 M은 model 이고 V는 View, W은 Whatever를 의미하여 무엇이든지 올 수 있다는 의미이다. Whatever에는 싱글톤패턴의 서비스, 팩토리 또는 필터, 디렉티브(Directive)등이 올 수 있다. 


  - Model : Controller의 $scope를 통한 자동 two-way binding 을 한다 

  - View : HTML에 $scope의 모델들을 설정하거나 앵귤러의 디렉티브를 이용하여 직관적인 설정한다  


  앵귤러의 데이터 모델은 바로 Scope에 의해서 연결이 되며, app.js 가 최초 수행 되면 $rootScope가 생성되고 그 하위로 $scope가 생성된다. 이들은 계층적으로 연결되어 상위의 scope 데이터에 접근할 수 있다. 만일 Chrome 브라우져를 사용한다면 scope 객체들의 계층구조를 보기 위하여 별도의 크롬도구를 설치해야 한다. 


 1) AngularJS Batarang: https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk 설치 

 2) 크롬 개발자도구 : 윈도우(Ctrl+Shift+i), 맥(Command+Option+i) 연다 

 3) 맨 우측의 AngularJS를 선택하고 Enable 옵션을 체크한다 

 4) < Scope (003) 에서 003은 앵귤러가 Scope를 생성하고 부여한 아이디이고 클릭하면 우측에 Scope에 저장된 정보를 볼 수 있다.



1. 화면별 컨트롤러 생성하기 

  Yeoman에서 앵귤러 프레임워크의 기능을 자동 생성해 주는 것은 yo 명령이다. 우리는 meanstack 애플케이션 생성을 위하여 "generator-angular"를 설치하였는데 yo 뒤의 명령은 관례에 따라 "yo angular:<기능> <명칭>"으로 사용할 수 있다. 


  - yo의 generator가 만일 generator-mean 이고 geneartor-mean안에 <기능>으로 controller가 있다면 

  - yo mean:controller sample 을 수행하면 보통은 컨트롤러 코드와 컨트롤러 테스트 코드가 자동 생성된다

  -즉 "yo <generator- 두의 명칭>:<genertor가 제공하는 기능명칭>  <사용자정의 명칭>" 으로 명령은 구성된다 

  - <사용자정의 명칭> 에서 이름을 지정할 때는 "user-biz" 라고 명칭을 주면 index.html에는 "user-biz.js" 파일이 추가 되고, 기능의 명칭은 "UserBiz<기능약어>" 식으로 명칭이 만들어 진다.


  generator-angular 기능 목록은 홈페이지를 참조한다 (https://github.com/yeoman/generator-angular) 컨트롤러 생성시 테스트 코드 파일이 자동으로 생성된다 

// yo angular:controller <명칭> 으로 각 메뉴의 컨트롤러를 생성한다.

$ yo angular:controller my-level

   create app/scripts/controllers/my-level.js

   create test/spec/controllers/my-level.js


$ yo angular:controller my-qna

   create app/scripts/controllers/my-qna.js

   create test/spec/controllers/my-qna.js


$ yo angular:controller my-following

   create app/scripts/controllers/my-following.js

   create test/spec/controllers/my-following.js


$ yo angular:controller tech-area

   create app/scripts/controllers/tech-area.js

   create test/spec/controllers/tech-area.js


$ yo angular:controller ranking

   create app/scripts/controllers/ranking.js

   create test/spec/controllers/ranking.js


$ yo angular:controller member

   create app/scripts/controllers/member.js

   create test/spec/controllers/member.js


$ yo angular:controller login

   create app/scripts/controllers/login.js

   create test/spec/controllers/login.js


// index.html에 자동으로 추가된 것을 볼 수 있다.

<!-- build:js({.tmp,app}) scripts/scripts.js -->

<script src="scripts/app.js"></script>

<script src="scripts/controllers/main.js"></script>

<script src="scripts/controllers/my-level.js"></script>

<script src="scripts/controllers/my-qna.js"></script>

<script src="scripts/controllers/my-following.js"></script>

<script src="scripts/controllers/tech-area.js"></script>

<script src="scripts/controllers/ranking.js"></script>

<script src="scripts/controllers/member.js"></script>

<script src="scripts/controllers/login.js"></script>

<!-- endbuild -->


  컨트롤러를 생성했는데 - 또는 . 를 사용하면 앞뒤의 문자들의 첫글자가 대문자로 변형되어 설정이 되고 뒤에 "Ctrl" 이 자동으로 붙는다 

// app/scripts/controllers/my-level.js 소스

'use strict';


angular.module('meanstackApp')

  .controller('MyLevelCtrl', function ($scope) {

    $scope.awesomeThings = [

      'HTML5 Boilerplate',

      'AngularJS',

      'Karma'

    ];

  });

  

  이제 app/scripts/app.js에 컨트롤러를 설정해 보자 

$stateProvider

      .state('main', {

        url: '/',

        templateUrl: 'views/main.html',

        controller: 'MainCtrl'

      })

      .state('login', {

        url: '/login',

        templateUrl: 'views/login.html',

        controller: 'LoginCtrl'

      })

      .state('mylevel', {

        url: '/mylevel',

        templateUrl: 'views/mylevel.html',

        controller: 'MyLevelCtrl'

      })

      .state('myqna', {

        url: '/myqna',

        templateUrl: 'views/myqna.html',

        controller: 'MyQnaCtrl'

      })

      .state('myfollowing', {

        url: '/myfollowing/{userId:[0-9]{1,4}}',

        templateUrl: 'views/myfollowing.html',

        controller: 'MyFollowingCtrl'

      })

      .state('techarea', {

        url: '/techarea/{techId:[0-9]{1,4}}',

        templateUrl: 'views/techarea.html',

        controller: 'TechAreaCtrl'

      })

      .state('gururanking', {

        url: '/gururanking',

        templateUrl: 'views/gururanking.html',

        controller: 'RankingCtrl'

      })

      .state('gurumember', {

        url: '/gurumember',

        templateUrl: 'views/gurumember.html',

        controller: 'MemberCtrl'

      });


  my-following이나 tech-area를 보면 아이디를 설정하였고 myfollowing.html에서 $stateParam을 사용하기위해 meanstackApp 모듈의 run() 에서 $rootScope에 $stateParams를 저장한다. 그리고 페이지가 전환 되기 시작할 때와 전환하여 로딩 완료되었을 때의 이벤트를 처리하기 위한 Listener를 등록한다. 

// myfollowing.html 

<div class="row">

  <div class="col-md-12">

    <h1>myfollowing.html</h1>

     User ID: {{$stateParams.userId}}

  </div>

</div>


// app/scripts/app.js

.run(['$rootScope', '$state', '$stateParams', 

  function ($rootScope, $state, $stateParams) {

    $rootScope.$state = $state;

    $rootScope.$stateParams = $stateParams;

  }]);


  - html에서 $stateParams 를 찾기위하여 자신의 $scope.$stateParams를 찾는다 없으면 다시 상위의 $rootScope.$stateParams을 찾게 된다. 따라서 클라이언트 애플리케이션 전역에서 사용할 내용들이 있다면 $rootScope에 저장한다. 

  - myfollowing 메뉴를 선택하면 Scope (009) 의 아이디값이 변경된다. 메뉴를 누를 때마다 Scope의 아이디가 변경되는데 이는 클라이언트 화면이 변경될 때 이전 Scope는 destroy되고 새로운 Scope가 생성되기 때문이다. 즉, 앵귤러가 화면 라우팅이되고 해당 화면의 컨트롤러에 신규 $scope 을 생성하여 DI (Dependency Injection)해준다 



2. 컨트롤러 코드 변경하기 

  애플리케이션 배포시에는 "grunt build"를 수행하는데 이때 .js에 대한 단축화(minification)이 수행되어 변수명을 알 수 없는 코드로 바꾸어준다. 앵귤러에서는 $로 시작하는 명칭이 변경이 되면 안되기 때문에 yo로 생성된 코드를 그대로 사용할 수 없다.  '$scope' 문자를 지정해 주면 function($scope) {} 가 최소화 되어 function($ab){} 로 바뀌어도 DI시에 '$scope'로 지정된 $scope 객체가 주입된다.

 angular.module('meanstackApp')

 .controller('MyFollowingCtrl', [

  '$scope',

  function ($scope) {

    ... 중략 ...

  }]);

  

  다른 컨트롤러도 해당 방식으로 전부 수정한다. 소스는 anuglar_setp02_insert-ctrl-menu 브랜치를 체크아웃한다 

https://github.com/MEAN-STACK/MEANStack-Bookwork/tree/anuglar_setp02_insert-ctrl-menu

posted by Peter Note
2014. 1. 29. 23:56 AngularJS/Start MEAN Stack

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


posted by Peter Note
2014. 1. 29. 07:48 AngularJS/Start MEAN Stack

Git, Node.js, Yeoman등 기본적인 개발환경을 설치하였다면 소프트웨어 요구사항 정의서 (Software Requirements Specification, SRS) 에서 정의한 화면을 CSS Framework을 사용하여 만들어 보자. 여기서는 가장 많이 사용하고 있는 Twitter Bootstrap을 사용토록 한다. 



사전준비

  이전 글에서 yeoman의 yo 명령을 통하여 angular 프로젝트를 "meanstack"이름으로 생성하였다. meanstack 하위에 app 폴더가 앵귤러 기반의 SPA (Single Page Application) 프론트앤드 애플리케이션의 ROOT 폴더가 된다. 


  - 반응형 웹 디자인 기반의 메뉴구조를 구성한다 

  - 메뉴를 클릭하면 해당하는 html 파일로 라우팅한다 

  - 메뉴에 아이콘을 추가한다 




1. Twitter Bootstrap 설치

 Twitter Bootstrap은 meanstack을 생성하면서 app/index.html에 자동 추가된다. 

  1) <!-- build:css ... --> : 주석은 제거하면 안된다. grunt build 시에 css 압축할 때 사용하는 주석이다. (build:js 도 동일) 

  2) <!-- bower:css .. --> : 주석은 제거하면 안된다. grunt build 시에 자동으로 bower install 한 내역을 추가한다. (bower:css 도 동일) 

// index.html

    <!-- build:css styles/vendor.css -->

    <!-- bower:css -->

    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />

    <!-- endbower -->

    <!-- endbuild -->


   .. 중략 ..


    <!-- build:js scripts/vendor.js -->

    <!-- bower:js -->

    <script src="bower_components/jquery/jquery.js"></script>

    <script src="bower_components/angular/angular.js"></script>

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <!-- endbower -->

    <!-- endbuild -->

  * "yo angular meanstack" 명령으로 twitter bootstrap이 자동 설치가 되면 twitter bootstrap이 사용하는 fonts 폴더를 app/fonts로 자동 복사해 준다. 이것은 grunt build시에 모든 css가 app/styles/*.css로 압축되고 bootstrap의 css가 사용하는 font는 ../fonts를 참조하기 때문이다. 따라서 배포를 위해서는 app/fonts 폴더가 존재 해야한다.




2. Angular UI-Bootstrap 설치

  Twitter Bootstrap을 html에 적용하려면 class 정보를 입력해야 한다. 이를 좀 더 직관적인 html tag 형식으로 쓸 수 있도록 AngularUI 팀에서 Directives를 만들었다. 예로 tab을 적용하가 위하여 <tab> 태그를 html에 사용하면 angular-bootstrap 모듈이 <tab> 를 class="tab" 형태로 바꿔서 DOM에 적용하는 것이다. html을 좀 더 직관적으로 작성할 수 있다는 장점이 있다. 

$ bower install angular-bootstrap --save

angular-bootstrap#0.10.0 app/bower_components/angular-bootstrap

└── angular#1.2.10


  bower를 통해 angular-bootstrap을 설치 후 "grunt build" 또는 "grunt serve" 를 실행하면 index.html에 필요 파일이 자동 추가된다. 자동 추가되는 파일의 정보는 bower_components/angular-bootstrap/bower.json 파일에 지정되어 있다.

// index.html 파일 

   <!-- build:js scripts/vendor.js -->

    <!-- bower:js -->

    <script src="bower_components/jquery/jquery.js"></script>

    <script src="bower_components/angular/angular.js"></script>

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>

    <!-- endbower -->

    <!-- endbuild -->


  angular-bootstrap은 별도의 앵귤러 모듈이므로 애플리케이션에서 사용하려면 모듈 의존성을 설정해야 한다. meanstack 서비스의 메인 소스는 app/scripts/app.js 이다. 여기에 모듈 의존성을 설정한다. 

  - 형식 : angular.module('<ApplicationName>', [<의존성 모듈 열거>]);

// app/bower_components/angular-bootstrap/ui-bootstrap-tpl.js 파일 상단

angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);



// app/scripts/app.js 안에 추가 

'use strict';

angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap'

])

  .config(function ($routeProvider) {

    .. 중략 ..

  });



3. Angular UI-Router 설치 

클라이언트 화면 전환을 위한 라우팅 기능은 앵귤러에서 제공하는 기본적인 라우터를 사용하지 않고 AngularUI팀에서 역시 제공하는 ui-router (https://github.com/angular-ui/ui-router)를 사용한다. ui-router는 한 화면에서 멀티 view 설정으로 원하는 부분들의 DOM을 변경처리할 수 있다. bower를 통해 ui-router v0.2.7 버전을 설치한다. ui-router v0.2.8 버전설치에 오류가 존재하기 때문이다. 또한 설치전에 Angular.js 버전을 현재 (2014.1.28기준) 최신버전인 v1.2.10 으로 업데이트한다.

// bower.json 파일에서 angular 관련 버전을 1.2.10 으로 지정한다 

$ vi bower.json

{

  "name": "meanstack",

  "version": "0.0.0",

  "dependencies": {

    "angular": "1.2.10",

    "json3": "~3.2.6",

    "es5-shim": "~2.1.0",

    "jquery": "~1.10.2",

    "bootstrap": "~3.0.3",

    "angular-resource": "1.2.10",

    "angular-cookies": "1.2.10",

    "angular-sanitize": "1.2.10",

    "angular-route": "1.2.10",

    "angular-ui-router": "0.2.7"

  },

  "devDependencies": {

    "angular-mocks": "1.2.10",

    "angular-scenario": "1.2.10"

  }

}


// angular 버전 업데이트

$ bower update 

angular-resource#1.2.10 app/bower_components/angular-resource

└── angular#1.2.10

.. 중략..

angular-scenario#1.2.10 app/bower_components/angular-scenario

└── angular#1.2.10


// ui-router 설치 1번을 선택한다 

$ bower install angular-ui-router#0.2.7 --save

    1) angular-ui-router#0.2.7 which resolved to 0.2.7

    2) angular-ui-router#~0.2.8 which resolved to 0.2.8 and has meanstack as dependants

[?] Answer: 1

angular-ui-router#0.2.7 app/bower_components/angular-ui-router

└── angular#1.2.10


   bower를 통해 ui-router를 설치 후 "grunt build" 또는 "grunt serve" 를 실행하면 index.html에 필요 파일이 자동 추가된다. 자동 추가되는 파일의 정보는 bower_components/angular-bootstrap/bower.json 파일에 지정되어 있다. 

    <!-- build:js scripts/vendor.js -->

    <!-- bower:js -->

    <script src="bower_components/jquery/jquery.js"></script>

    <script src="bower_components/angular/angular.js"></script>

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>

    <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>

    <!-- endbower -->

    <!-- endbuild -->


  ui-router 또한 별도의 모듈이므로 app/scripts/app.js 안에 모듈 의존성을 설정한다 

'use strict';


angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap',

  'ui.router'

])

  .config(function ($routeProvider) {

  .. 중략 ..

  });



4. FontAwesome Icon 설치

  다양한 아이콘을 사용하기 위하여 bootstrap과 별도로 fontawesom(http://fortawesome.github.io/Font-Awesome/) 라이브러리를 설치한다. 텍스트 정보를 좀 더 직관적으로 인지할 수 있도록 다양한 아이콘을 적용할 수 있다. 

$ bower install font-awesome --save

bower font-awesome#*            cached git://github.com/FortAwesome/Font-Awesome.git#4.0.3

font-awesome#4.0.3 app/bower_components/font-awesome


  설치 후 app/bower_components/font-awesome/ 폴더 밑으로 bower.json 이 존재하지 않으므로 필요한 파일을 index.html에 직접 기입해야 한다. index.html에 font-awesome.css 파일을 추가하자. 추가시 <!-- bower:css --> 영역에 넣으면 grunt build(grunt serve)시에 직접 넣은 정보는 자동 삭제되므로 <!-- bower:css --> 가 없는 <!-- build:css --> 안에 추가한다. 

     <!-- build:css styles/vendor.css -->

    <!-- bower:css -->

    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />

    <!-- endbower -->

    <!-- endbuild -->


    <!-- build:css({.tmp,app}) styles/main.css -->

    <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css" />

    <link rel="stylesheet" href="styles/main.css">

    <!-- endbuild -->


  'grunt build'를 하게 되면 font-awesome의 css는 app/styles/main.css 파일로 묶이게 된다. font-awesome.css 소스를 보면 css 상위의 fonts 폴더의 파일을 참조한다. 따라서 font-awesome의 fonts 폴더안의 파일들을 app/fonts 밑으로 복사한다.

Twitter Bootstrap과 Font awesome의 css가 참조하는 파일들 목록

$ cd app/fonts

$ ls 


fontawesome-webfont.woff

fontawesome-webfont.ttf

fontawesome-webfont.svg

fontawesome-webfont.eot

FontAwesome.otf

glyphicons-halflings-regular.woff

glyphicons-halflings-regular.ttf

glyphicons-halflings-regular.svg

glyphicons-halflings-regular.eot


이제 필요한 라이브러리들이 index.html에 추가되었다. 다음장에서는 Twitter Bootstrap을 이용하여 반응형 웹 디자인(Responsive Web Design : RWD) 메뉴와 전체 화면 레이아웃을 만들어 보자.

posted by Peter Note
2014. 1. 26. 07:50 AngularJS/Start MEAN Stack

yeoman은 자바스크립트 기반의 프론트앤드 개발을 도와주는 자동화 툴이다. 자바에서 요즘 많이 사용되는 Maven과 유사하다. 



1. 사용이유

  서비스 개발의 규모가 커지고 자바스크립트의 각 모듈별의 버전 의존성을 관리하려면 어떻게 해야 할까? 그리고 필요한 자바스크립트 모듈을 다운로드하기 위하여 각 사이트를 방문하여 다운로드 해야할까? 기본적인 코드 골격과 코딩 컨벤션은 최초에 어떻게 해야할까? 개발하고 나서 테스트 및 빌드 자동화는 어떻게 해야할까? 이러한 의문에 대한 해답을 주는 것이 Yeoman이다.

  - 스케폴딩 생성 : 기본적인 코드 골격을 만들어 주고 이후 추가되는 앵귤러 코드는 명령을 통해 생성한다 

  - 라이브러리 의존성 관리 : 프론트앤드에서 사용하는 다양한 jquery 모듈과 angular.js 모듈의 설치 및 버전 관리를 한다 

  - 테스트 및 빌드 자동화 : 테스트 코드를 작성하였다면 테스트 수행 및 코드 압축과 문법오류 검사 그리고 배포파일의 생성을 한다 

  - 사전 점검 : 프론트단의 코드를 Node.js기반 수행하여 브라우져에서 개발한 화면을 사전에 테스트 점검 가능하다



2. 설치하기  

  설치는 의외로 간단하다. 사전 준비로 Node.js를 설치 하였다면 NPM (Node Package Manager)을 통해 설치한다. yo, bower, grunt 의 특징은 자바스크립트로 개발하여 node.js기반위애 구동하는 도구이다 

// 1) yo 설치 

$ npm install -g yo


// 2) bower 설치 

$ npm install -g bower


// 3) grunt 설치 

$ npm install -g grunt-cli

  * http://yeoman.io, http://bower.iohttp://gruntjs.com 에서 설치관련 자세한 사항을 참조한다. 



3. 주요 기능 

  yeoman은 크게 3가지 기능으로 구분된다. 각 기능의 구분이 명확하고 사용하는 목적이 틀리기 때문에 잘 알아 두기 바란다. 기본적으로 yo를 yeoman팀에서 자체적으로 만들었고, bower와 grunt는 이미 존재하였던 도구로써 yeoman이라는 이름으로 통합한 것이다. 따라서 bower와 grunt를 별개의 독립적인 도구로 사용할 수도 있다


  yo 

  - 프론트앤드 개발을 위한 기본 구조를 만들어 준다. yeoman에서는 스켈폴딩 코드를 만들어 준다고 말한다 

  - 스케폴딩 파일을 생성하려면 목적에 맞는 제너레이터를 사전에 설치해야 한다.

    1) 제너레이터 명칭 구성 : generator-<UserDefine> 로서 UserDefine 명칭을 정한다. 예) generator-angular

    2) npm 을통하여 글로벌 설치한다 

    3) UserDeinfe 명칭이 angular 라면 yo 명령 다음에 제너레이터의 구분을 위하여 항시 해당 UserDefine 명령이 온다 

        yo angular <Subject>  또는 yo angular:<SubCommand>  <Subject>

    4) yo angular <Subject> 를 통해 초기 프로젝트의 명칭을 정하고 기본 골격을 생성한다 

    5) yo angular:<SubCommand> <Subject> 를 통해 기본골격 밑으로 기능을 추가한다. SubCommand는 제너레이터에 따라 사용자 정의 할 수 있다 

  - 프로젝트에 필요한 골격 코드 생성을 위한 자신만의 제너레이터를 만들 수 있다. 

  - 사용법

    1) angular 제너레이터 설치

    2) 프로젝트 폴더생성

    3) angular 프로젝트 생성을 하고 Sass 미사용, Bootstrap을 사용체크하면 필요한 파일과 모듈을 자동 설치한다

    4) 프로젝트 기본 골격코드 자동 생성하면 app 폴더가 프론트 개발 ROOT 폴더가 된다

// 1) 

$ npm install -g generator-angular


// 2) 

$ mkdir meanstack & cd meanstack


// 3) 

$ yo angular meanstack

     _-----_

    |       |

    |--(o)--|   .--------------------------.

   `---------´  |    Welcome to Yeoman,    |

    ( _´U`_ )   |   ladies and gentlemen!  |

    /___A___\   '__________________________'

     |  ~  |

   __'.___.'__

 ´   `  |° ´ Y `


Out of the box I include Bootstrap and some AngularJS recommended modules.


[?] Would you like to use Sass (with Compass)? No

[?] Would you like to include Twitter Bootstrap? Yes

[?] Which modules would you like to include? (Press <space> to select)

❯⬢ angular-resource.js

 ⬢ angular-cookies.js

 ⬢ angular-sanitize.js

 ⬢ angular-route.js


// 4) 


bower

  - bower는 트위터에서 개발한 프론트앤드 라이브러리 설치 및 버전 의존성 관리 도구이다 

  - yo를 통하여 스케폴딩 파일이 생성하면 bower 사용을 위한 기본 환경파일도 자동 생성된다 

    1) 환경파일은 bower.json 으로 현재 설치된 라이브러리 명칭과 버전을 자동 기록한다 

    2) 라이브러리 설치 위치정보는 .bowerrc 에서 변경한다. 기본값으로 "bower_components" 를 사용한다 

// 1) bower.json 최초 설치 정보 

{

  "name": "meanstack",

  "version": "0.0.0",

  "dependencies": {

    "angular": "1.2.6",

    "json3": "~3.2.6",

    "es5-shim": "~2.1.0",

    "jquery": "~1.10.2",

    "bootstrap": "~3.0.3",

    "angular-resource": "1.2.6",

    "angular-cookies": "1.2.6",

    "angular-sanitize": "1.2.6",

    "angular-route": "1.2.6"

  },

  "devDependencies": {

    "angular-mocks": "1.2.6",

    "angular-scenario": "1.2.6"

  }

}


// 2) .bowerrc 위치정보

{

    "directory": "app/bower_components"

}


  - 사용법 

   1) 검색 : "bower search <명칭>"

   2) 설치 : "bower install <명칭>  --save (또는 --save-dev)"

   3) 보기 : "bower list"

   3) 도움말 : "bower help" 또는 "bower help <명령어>"

$ bower list

bower check-new     Checking for new versions of the project dependencies..

meanstack#0.0.0 ~/meanstack

├── angular#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

├─┬ angular-cookies#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

├─┬ angular-mocks#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-resource#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-route#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-sanitize#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-scenario#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ bootstrap#3.0.3

│ └── jquery#1.10.2 (2.1.0 available)

├── es5-shim#2.1.0 (latest is 2.3.0)

├── jquery#1.10.2 (latest is 2.1.0)

└── json3#3.2.6 (latest is 3.3.0)

  * angular 개발시점에 정식 릴리즈된 가장 최신버전을 사용할 예정이고 bower를 통하여 업데이트할 것이다. 


Grunt

  - grunt는 자바의 ant와 같은 기능을 수행한다

  - 다양한 플러그인을 통하여 기능을 첨부하여 확장할 수 있다. 

  - yo을 통해 프로젝트 골격 코드생성시 Gruntfile.js 환경파일이 기본 생성된다 

  - 또한 골격 코드 생성시 기본적으로 사용되는 플러그인은 node_moudles 폴더에 자동 설치된다 

// 1) node_modules에 기본 설치된 grunt 플로그인 


// 2) Gruntfile.js 내역 중 명령 일부

module.exports = function (grunt) {

  // Load grunt tasks automatically

  require('load-grunt-tasks')(grunt);


  // Time how long tasks take. Can help when optimizing build times

  require('time-grunt')(grunt);


  // Define the configuration for all the tasks

  grunt.initConfig({


    // Project settings

    yeoman: {

      // configurable paths

      app: require('./bower.json').appPath || 'app',

      dist: 'dist'

    },

    ... 중략...


grunt.registerTask('serve', function (target) {

    if (target === 'dist') {

      return grunt.task.run(['build', 'connect:dist:keepalive']);

    }


    grunt.task.run([

      'clean:server',

      'bower-install',

      'concurrent:server',

      'autoprefixer',

      'connect:livereload',

      'watch'

    ]);

  });


  grunt.registerTask('server', function () {

    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');

    grunt.task.run(['serve']);

  });


  grunt.registerTask('test', [

    'clean:server',

    'concurrent:test',

    'autoprefixer',

    'connect:test',

    'karma'

  ]);


  grunt.registerTask('build', [

    'clean:dist',

    'bower-install',

    'useminPrepare',

    'concurrent:dist',

    'autoprefixer',

    'concat',

    'ngmin',

    'copy:dist',

    'cdnify',

    'cssmin',

    'uglify',

    'rev',

    'usemin',

    'htmlmin'

  ]);


  grunt.registerTask('default', [

    'newer:jshint',

    'test',

    'build'

  ]);

};


  - 사용법

   1) 테스트 : grunt test

   2) 빌드 : grunt build

   3) 프리뷰 : grunt serve

// 1) 

$ grunt test

Running "clean:server" (clean) task


Running "concurrent:test" (concurrent) task


    Running "copy:styles" (copy) task

    Copied 1 files


    Done, without errors.



    Execution Time (2014-01-25 22:42:19 UTC)

    loading tasks   4ms  ▇▇▇▇ 25%

    copy:styles    10ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 63%

    Total 16ms


Running "autoprefixer:dist" (autoprefixer) task

Prefixed file ".tmp/styles/main.css" created.


Running "connect:test" (connect) task

Started connect web server on 127.0.0.1:9001.


Running "karma:unit" (karma) task

INFO [karma]: Karma v0.10.9 server started at http://localhost:8080/

INFO [launcher]: Starting browser Chrome

WARN [watcher]: Pattern "/Users/nulpulum/prototyping/yeomain/meanstack/test/mock/**/*.js" does not match any file.

INFO [Chrome 32.0.1700 (Mac OS X 10.9.1)]: Connected on socket nUaNX14YFrQJ_aeDkWM5

Chrome 32.0.1700 (Mac OS X 10.9.1): Executed 1 of 1 SUCCESS (2.184 secs / 0.033 secs)


Done, without errors.



Execution Time (2014-01-25 22:42:17 UTC)

concurrent:test  2.2s  ▇▇▇▇▇▇▇▇▇▇▇26%

karma:unit       6.2s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 73%

Total 8.4s


// 2) 브라우져가 자동 실행된다 

$ grunt serve 


Yeoman에 대한 이해와 기본적인 설치, 사용법을 익혔다. 프론트 앤드 개발을 하면서 계속 사용하게 되므로 잘 익혀 놓도록 하자 


posted by Peter Note
2014. 1. 17. 13:12 카테고리 없음

운영중이 사이트가 있다면 이제 손쉽게 안드로이드 실행파일을 만들어 보세요. 


http://www.appsgeyser.com/


해당 블로그 사이트를 .apk로 만들어 보았습니다. 1분만에 만들어 지네요. 안드로이드에 다운로드하여 설치하세요. 

이제 모바일 컨버젼스 블로그를 앱으로...^^


MobiconSoft.apk



posted by Peter Note
2014. 1. 2. 13:06 AngularJS/Start MEAN Stack

개인 블로그를 통하여 정보를 공유하는 일에서 한발 더 나아가 함께 글을 올리고 글에 대한 Q&A를 하는 웹앱 서비스를 만들어 보고 싶다. 내가 로그인을 했을 때 처음 보여지는 UI는 SNS 형태이며 내가 올린 질문(Question)에 대한 답변이나 커뮤니티안에서 활동하는 재능 기부자를 팔로잉 하면 해당 글들이 먼저 보여졌으면 좋겠다. 즉, 글에 대한 개인화를 원하며 재미있는 재능 기부 놀이를 해보고 싶다. 




1. 기존 커뮤니티 서비스

  - 특정 재능 기부자가 글을 연재하여 올린다

  - 여러 Q&A 게시판 형태를 취한다  

  - 내가 원하는 것을 Search 하여 찾아 보아야 한다 

  - PC에 최적화 되어있다. 어떤 것은 모바일에서 볼 수도 있다

   글올렸으니 알아서 찾아 읽어봐 ^^;



2. MEAN Stack 커뮤니티 서비스 

  - SNS 형태를 취하여 모바일기기(Tab, Phone) 중심으로 만들어진다 

  - MEAN Stack에 대한 글을 연재하는 재능 기부 개발자들이 있고, 이중 몇명을 following 할 수 있다.

  - 내가 연재글이나 별도의 란에 질문(Question)을 달면 MEAN Stack 재능 기부 개발자들에게 자동 Notify가 된다. 

    (단, 별도의 질문은 지정된 분류만을 할 수 있다. Concern Area)

  - 답변을 준 재능 기부자에게 점수를 통해 고마움을 표현하면 해당 점수는 재능 기부자의 인기도 순위에 영향을 준다.

  - 또한 연재 글에 대해서도 모든 사람은 한번 점수를 줄 수 있다.

  - 인기도는 천민, 일반, 귀족, 왕족, 신(구루) 등의 구분으로 명예의 전당에 공개되어 구루로 추앙된다. 게임처럼 놀고 싶다. 

  - 해당 인기도는 한달에 한번 reset되고 이달의 구루에겐 모종의 선물을 수여한다.(뭘 주지? ^^)

  - 즉, 답변이나 글연재를 많이 한다고 구루가 되지 않고 해당 답변, 글에 메겨진 점수를 통해 인기도 순위와 등급은 결정된다 

  - 가입한 누구나 특정 분류의 재능 기부자가 될 수 있다 

   재능 기부하면서 함께 재미 있게 놀고 싶다. 



3. Mockup

  - 서비스의 명칭 

    + Community Planet : 새로운 혹성의 커뮤니티. 점수통해 자신만의 영역을 만들어 간다. 우리는 디지털 이주민.

      

  - 메인 화면 

    + 좌측은 모바일의 Sliding 메뉴로 구성된다 

    + 로그인을 하면 우측 상단에 Push Message 건수와 로그인/로그아웃 링크가 나온다 

    + 중앙에는 내가 질문한 것에 대한 답변과 다른 질문/답변들 그리고 Following 한 재능 기부자들의 글목록이 나온다

    + 글 목록은 핀터레스트 스타일 이다 


  - Question & Answer

    + 내가 질문한 것과 답변 받은 것을 확인 할 수 있다 

    + 질문내역을 클릭 하면 질문에 대한 답변을 볼 수 있다. 

    + 답변에 대한 점수를 줄 수 있고, 답변이 틀리면 마이너스 점수도 줄 수 있다 


  - Following 재능 기부자들의 글 보기

    + 팔로어한 사람별의 최근 글을 볼 수 있다. 내가 관심갖는 글만 보기 

    + 보는 방식은 Q&A화면과 유사하다 

    + 연재글에 역시 질문과 답을 달 수 있다 

 

  - Concern Area

    + 관심영역은 현재 커뮤니티 서비스 관리화면에서 목록을 만들 수 있다. (마치 워드프레스의 관리화면처럼-향후 개발)

    + 특정 관심 영역을 들어가면 해당 영역에서 활동하는 모든 글들을 볼 수 있다.

    + 검색하여 글을 찾을 수 있다 


  - 구루구루

    + 이곳은 명예의 전당이다 

    + 가장 점수가 높은 재능 기부자를 상위에 놓고 점수가 높은 순으로 글목록이 나열 된다 

    + 그리고 이번달의 글과 질문 답변에 대해 볼 수 있다 



다음의 비디오를 참조 했으면 좋겠다. Community Planet은 바로 이 정신이다. 


posted by Peter Note
2013. 12. 15. 13:38 AngularJS/Start MEAN Stack

개발자들이 가장 힘들어 하는 부분은 UI 개발이라 생각한다. 더 나아가 UX(User eXperience)를 어떻게 UI속에 녹여내어 편의성과 제품의 가치에 맞는 감성을 넣을 수 있을지도 고민해 보아야 한다. 막상 클라이언트 개발을 시작하였지만 마땅히 디자이너의 도움 없이도 완성도 있는 서비스를 만들고 싶다면 CSS Framework을 사용해 보자. 


요즘 추세는 크게 두가지 측면으로 움직이는 것 같다. 하나는 웹의 모양과 색감등 보여지는 부분을 담당하는 CSS를 프레임워크화하여 일관된 UX를 전달해주고 있고 이를 공개하고 있다. 이중 가장 유명한 것이 트위터 부트스트랩이다. 버전 3.0 에서는 반응형 웹 디자인(RWD, Responsive Web Design)이 기본으로 적용되어 모바일 스마트기기의 해상도에 따라 화면이 최적화되어 표현될 수 있도록 하는 기능을 제공한다. 둘째는 플랫디자인이다. 구글이나 애플에서 주도를 하고 있고 간결하면서 사용성을 늘린 디자인으로 보인다. 기본 트위터 부트스트랩을 확장하여 플랫디자인 CSS를 적용한 다양한 CSS 프레임워크를 제공하고 있다. 


따라서 트위터 부트스트랩을 기본으로 하여 확장한 CSS 프레임워크를 사용하게 되면 '반응형 웹 디자인' 과 '플랫디자인'을 동시에 충족하여 적용할 수 있게 된다. 두가지 요소에 대해서는 이곳의 주제와 약간 벗어나므로 좀 더 전문적인 서적을 통해 개념과 상세 기능을 익히기 바란다. 여기서는 선택한 CSS 프레임워크를 어떻게 적용하고 활용하는지 중점적으로 보도록 하겠다. 



1. 반응형 웹 디자인 고려

  - Fluid Layout : 화면 사이즈에 따라 배치된 요소들의 정렬을 재 배치하는 것이다. 화면이 작아진다고 하여 좌우 스크롤이 생기는 것이 아니라 요소가 밑으로 흘러들어 간다. 이를 위하여 부트 스트랩은 Grid 12 컬럼으로 요소 배치 및 플루이드 정렬을 할 수있다 

  - Responsive Menu : 부트 스트랩은 상단에 메뉴를 놓고 해상도에 따라 메뉴의 형태를 자동 변경해 준다 

서비스 개발이 위의 두가지를 먼저 유념하여 사용하도록 한다. 기본적인 사용법은 트위터 부트스랩의 홈페이지를 참조하자 



2. 플랫 디자인에 대한 고려

애플은 iOS7에서 플랫디자인을 선보이고, 구글은 전사 서비스를 플랫디자인으로 도배하고 있습니다. 프러덕트 디자이너가 Visual 단계에서 바라본 플랫디자인의 요소 5가지를(참조) 유념하여 개발자가 UI와 UX를 플랫 디자인으로 진행할 때 고려해야할 사항에 대해 미리 알아두면 좋을 것 같습니다.

     + Use of simple elements 

        심플한 아이콘 : 사각, 원형의 단순 모형에 배치, 컬러, 형태로 표현 -> 사용자가 UI 를 쉽게 인식하고 사용토록 만든다 

     + Absence of depth 

        배제된 효과 : 그림자, 입체감, 그라데이션등의 현실감있게 하는 3D적 효과를 배제하여 직관적으로 만든다  

     + Typography

        타이포그래피 : 화려한 폰트를 배제하고 간결한 폰트를 사용한다. 예) 산세리프 

     + Color

        단순한 칼라 : 2~3가지 또는 5~6가지의 색상만을 사용하여 간결하고 과감하고 표현한다. 핑크그린블루가 대세  

     + Minimalism

        미니멀리즘 : 미니멀리즘과 부합 - 간단하고 심플하게 표현으로 다양한 웹, 모바일에 적응이 쉽다  



3. 서비스 디자인으로 나아가야 한다 

  서비스을 만들 때 개발자는 어떻게 접근을 하고 있을까요? 아마도 대부분 디자이나 웹 퍼블리셔가 만들어준 화면에 코딩을 하는 정도를 경험했으리라 봅니다. 하지만 자신의 웹앱 서비스를 만들기 시작한다면 단지 디자인이 문제가 아니라 UX라는 사용자 경험을 고려하여 사람과 컴퓨터의 상호작용을 생각해 보아야 하고, 우리가 만들려는 프로덕트의 가치를 서비스에 어떻게 녹여 내야 하는지 연구하고 새롭게 창조하는 과정을 거쳐향 합니다. 이를 줄이면 서비스 디자인이라고 말하며 서비스 개발전 우리가 사용하는 도구와 프레임워크들이 가치를 창출하기 위하여 어떻게 쓰여야하는지 큰 뷰에서 설명을 해주고 있습니다. 인식의 전환을 통하여 중요한 가치를 찾고 반응형 웹 디자인이나 플랫 디자인이 왜 나왔는지 이해하여 보는 것도 재미날 것같습니다. 한국디자인진흥원의 윤성원님이 이야기하는 서비스 디자인 개념 자료를 한번 보시면 쉽게 이해 되리라 생각합니다. 


  - 서비스 디자인 이란 무엇인가? - 개념 이해


posted by Peter Note
2013. 12. 5. 13:34 AngularJS/Start MEAN Stack

이전 블로그에서 MEAN Stack에 대한 의미를 알아보았습니다. Angular.js 의 Why, How, What을 살펴보도록 하겠습니다.


1. Angular.js 가 주고자 하는 가치는 무엇인가?

  앵귤러는 모던 애플리케이션을 개발하기 위한 프레임워크입니다. 복잡하고 단순 반복적인 작업을 대폭 줄여줌으로써 신속한 개발을 가능하게 해줍니다. 이를 통해 보다 빨리 고객의 피드백을 받고 개선할 수 있는 에자일한 개발 진행이 가능해 집니다. 보다 적은 작업을 통해 보다 더 많은 행복을 주는 프레임워크입니다. 


  1989년 HTML을 시작으로 2005년 Ajax가 나오고 이후 jQuery를 통하여 DOM 을 조작을 통한 웹서비스 개발 시대를 거쳐 지속적인 브라우져 기술의 성숙과 JavaScript 해석기의 성능향상으로 이제는 자바스크립트를 통해 클라이언트에서도 서버와 같은 MVC 패턴 방식의 애플리케이션 개발이 가능해 졌습니다. 만일 jQuery만을 통해 웹앱을 개발한다고 생각하면 HTML 페이지마다 들어가는 서버코드와 자바스크립트들의 복잡한 코드에 머리를 쥐어 짤지도 모릅니다. 

  저는 13년을 넘게 자바언어로 서버만을 개발했었습니다. 가끔 클라이언트단의 jQuery 코드를 볼 때마다 저 영역으로는 절대 들어가지 말자라고 생각했습니다. 왜일까요? 그것은 서버처럼 잘 정비된 애플리케이션 프레임워크도 없으며, 서버코드와 자바스크립트를 HTML 사이사이에 끼워 넣으며 스파게티같은 코드를 짜야 하기 때문이었습니다. 

  그러나 2010년 이후부터 상황은 바뀌었습니다. Backbone.js가 이러한 복잡함을 해결하고자 초기 프레임워크로 나왔고, 이후 Ember.js 그리고 Angular.js 에서 Meteor.js 까지 모던 웹앱을 개발할 수 있는 프레임워크가 나왔습니다. 앵귤러는 어떻길래 이들 프레임워크 중 단연 선풍적인 인기를 누리고 있는 걸까요?


  - .js F/W  트랜드 



- .js 에서 dot(점)을 빼고 Backbonejs 와 Angularjs 트랜드 


  Backbonejs 와 Angularjs(노란색) 의 트랜드 변화는 급상승 중



2. Angular.js 가 어떻길래 개발자들이 열광하는가?

  앵귤러의 아버지인 미스코님(Misko Hevery)의 소개 동영상을 잠시 감상해 봅시다 


  요즘 SI시에 서버개발을 위하여 Spring Framework와 iBatis(myBatis)를 사용하는 것이 기본적인 관례처럼 되었는데요. 이들의 기능을 잠깐 생각해 볼까요. 

  - DI (Dependency Injection) 을 통하여 코드간의 결합도를 줄여주고, 테스트 코드의 작성을 쉽게 해줍니다. 

  - MVC 패턴를 통하여 기본적인 서버 개발의 틀을 가이드 해줍니다.

  - 다양한 라이브러리와 툴의 결합으로 개발 생산성을 높여줍니다.


 이를 앵귤러 입장에서 생각해 보면 정확히 위와 같은 요구사항을 충족해 주고 있습니다. 

  - DI를 지원합니다. 모듈단위의 개발로 코드를 간결하게 유지하고 테스트를 쉽게 해줍니다.

  - MV* 패턴을 통하여 역할을 나누고 아키텍쳐 Layered 개발을 가능하게 해줍니다. 당연히 유지 보수가 쉬워지겠죠

  - jQuery의 Plugins 포함한 기존의 다양한 라이브러리를 재 사용할 수 있습니다. 

  - 서버의 Maven, Ant 와 같은 관련된 라이브러리 의존성 관리 및 빌드 자동화 툴과 결합하여 개발 생산성을 높일 수 있습니다. 


  하지만 앵귤러를 시작하기 전에 하나의 선입견을 버리고 새로운 인식으로 접근을 해야 합니다. 자바스크립트는 이제 브라우져에서 화면의 단순 조작을 통한 효과에 쓰이는 언어가 아닌 진정한 엔터프라이즈급 애플리케이션부터 모바일 웹앱까지 모던한 애플리케이션을 만드는데 가장 많이 쓰이고 있는 개발언어가 되었다는 것입니다. 구글은 크롬앱을 코르도바(폰갭)를 통하여 안드로이드 및 iOS에서 구동하는 툴킷을 2014년 초에 출시할 계획입니다. 크롬앱은 자바스크립트로 개발합니다. 이는 본격적으로 네이티브 모바일 앱과 자바스크립트 모바일 웹앱이 함께 공존할 수 있는 시기가 왔다는 것을 암시합니다. 


 앵귤러(Angular.js)는 화면을 조작하는 라이브러리나 화면을 조작하는 프레임워크가 아니 모던 애플리케이션을 개발하는 프론트엔드 프레임워크입니다. 최근에는 이를 SPA(Single Page Application) 개발이라 부릅니다. Adobe의 Flex 기술을 통하여 클라이언트단에 엔터프라이즈 애플리케이션을 개발하는 RIA(Rich Internet Application) 가 선풍적인 인기를 누렸음을 잘 알것입니다. 인터넷이 되는 스마트 기기 에서 Adobe Flash의 공식 미지원 발표에 있은 후 이제 RIA의 용어는 점점 잊혀져 가고 있습니다. 하지만 사람들의 요구는 UX에 점점 더 목말라하고 있습니다. 단순한 웹서비스로는 이에 대응하기 힘들며 PC시대의 RIA를 표방한 Adobe의 Flex 프레임워크처럼 현재의 PC와 Mobile을 통합하는 진정한 RIA와 같은 자바스크립트 진영의 개발 프레임워크가 바로 앵귤러(Angular.js) 입니다. 무엇을 제공하 길래 앵귤러는 SPA 프레임워크의 큰형님이 되고 있는 걸까요?


 SPA vs RIA 비교 



3. Angular.js에는 무엇이 있는가?

  앵귤러는 클라이언트의 애플리케이션을 견고하게 만들 수 있는 방법을 제시하고 있습니다. 클라이언트의 생명은 바로 UX에 있다고 생각합니다. 훌륭한 사용자 경험을 주기 위하여 앵귤러 사용한다면 빠르게 개발하고 사용자의 피드백을 받아 개선해 갈 수 있습니다.


  견고한 싱글 페이지 웹 애플리케이션(SPA)을 만들기 위하여 앵귤러는 다음과 같은 기능을 제공합니다. 

  - 모듈 단위 개발을 통하여 글로벌 영역을 오염시키지 않고 모듈 단위 개발을 가능하게 해줍니다. 따라서 대규모의 애플리케이션 확장이 가능해 집니다. 


  - 양방향 데이터 바인딩을 통하여 View(HTML) 와 Controller(자바스크립트) 사이에 데이터에 대한 양방향 동기화를 자동으로 해줍니다. 즉, View 에서 데이터를 입력하면 Controller 단의 데이터를 업데이트 해주고, 그 반대도 가능해 집니다. 기존 jQuery에서는 데이터의 동기화를 위하여 Event와 Listener를 등록하는 코드를 모두 개발해야 했다면 앵귤러에서는 이런 코드를 찾아 볼 수 없습니다. Flex의 [Bindable]을 생각하면 됩니다. 


  - 앵귤러를 보통 MVW 프레임워크라 부릅니다. W는 Whatever의 의미로 Controller, Service등 무엇이든 올 수 있다는 것입니다. 역시 가장 중요한 것은 Model과 View의 연동을 양방향으로 함으로써 UX를 보다 쉽고 빠르게 개발토록 하는데 있습니다. 


  - Controller는 View HTML에서 발생한 이벤트에 따라 Model 변경을 제어하고, Service는 백앤드 서비스를 데이터 I/O 통신을 담당합니다. 각 필요한 기능은 DI(Dependency Injection)을 통하여 펑션의 파라미터 주입방식으로 인스턴스를 받아서 사용합니다. 


  - SPA 의 Single Page라는 의미는 최초에 index.html 을 서버로부터 받은 후 부분적인 화면의 변경을 위하여 부분 HTML(Partial HTML)만을 받아서 index.html의 특정 영역의 DOM 객체 변경을 통해 View의 일부분을 바꿔줍니다. 이후 서버로 부터 받는 것은 View의 컨텐츠 데이터인 JSON 이나 XML 입니다. 이렇게 화면의 부분 변경을 위하여 Routing 기능을 제공합니다. 


  - jQuery + PHP, jQuery + JSP 코드가 들어간 HTML 이 기존의 코딩 방식이라면 Javascript + HTML 만 존재하는 것이 앵귤러의 코딩 방식이다. 또한 반복해서 사용하는 HTML 또는 자바스크립트 코드조각이나 위젯/플러그인을 앵귤러만의 컴포넌트로 만들어서 HTML tag, attribute, class, comment 등의 방식으로 HTML에 포함시킬 수 있는 Directives(지시자) 기능을 제공합니다. Flex의 MXML에 차트관련 컴포넌트를 <Line> 태그로 표현하듯이, Angular.js의 Directive(컴포넌트)를 만들면 HTML안에 <Line> 과 같은 태그를 포함시킬 수 있습니다. 즉, Directive는 HTML 태그로 표기할 수 있는 컴포넌트 모듈을 만들 수 있고 Callback 펑션 기능을 내부로 숨기고 재사용할 수 있는 것이다. 


  - 백앤드로 Node.js를 사용한다면 Flex의 Messaging 기술과 같은 Push 방식의 구현을 Socket.io 모듈을 통하여 보다 더 쉽게 구현 할 수 있다. 


이제 자바스크립트 코딩이 가능하다면 싱글 페이지 애플리케이션(SPA) 에 도전해 봅시다.



<참조>

  - 스타트업을 위한 서비스 개발 기술 - MEAN Stack

  - 웹 애플리케이션 견고하게 만들기

  - 프론트 엔드 웹앱 프레임워크 (SlideShare)

posted by Peter Note
2013. 12. 4. 01:20 My Projects/BI Dashboard

상단의 대메뉴를 선택한 후 본문의 왼쪽에 있는 소메뉴를 클릭하면 오른쪽에 본문의 내용이 나온다. 본문을 보기까지의 라우팅(Routing)을 보면 대메뉴 클릭 -> 소메뉴 클릭 -> 본문 으로 이어지는 DOM의 변경이 일어난다. jqGrid, HigChart등의 json 환경 내역을 관리하는 화면을 만들기 위하여 Routing 방식을 고도화 해보자




1. Nest View

  - 큰메뉴 -> 작은 메뉴 -> 더 작은 메뉴 -> 본문 식의 View가 내제되어 라우팅 될 경우

  - UI-Router 를 이용한 데모, Plunker 데모

  - Route-Segment 를 이용한 데모



2. Admin 화면 구성하기

  - index.html 메뉴 추가하기

  - component에서 jqgrid, highchart와 같은 컴포넌트의 config 값 (json type)을 관리한다

// index.html 메뉴 추가하기 

<div class="collapse navbar-collapse">

  <ul class="nav navbar-nav">

    <li data-ng-class="activeWhen(path()=='/')">

      <a href="" data-ng-click="setRoute('/')">Home</a>

    </li>

    .. 중략 ..

    <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">Admin <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Project</li>

          <li><a href="">User</a></li>

          <li><a href="">Customer</a></li>

          <li><a href="">Code</a></li>

          <li><a href="">Role</a></li>

          <li class="divider"></li>

          <li class="dropdown-header">MobiconSoft</li>

          <li data-ng-class="activeWhen(path()=='/mc-component')">

      <a href="" data-ng-click="setRoute('/mc-component')">Component</a>

    </li>

          <li><a href="">Dashboard</a></li>

        </ul>

    </li>

  </ul>

</div>  



// Component partial 화면과 모듈 생성 및 등록

// 1) MCAdminCtrl.js 모듈을 만들기 

// 2) views/mc/component.html 화면 만들기

// 3) DashboardApp.js 에 MCAdminCtrl.js 모듈 명칭 등록하기 : DashboardApp.MCAdminCtrl

// 4) index.html 에 MCAdminCtrl.js 모듈 파일 <script> 태그 추가하기

  - 서버 코드로 ComponentController / Component / ComponentService / ComponentMapper(java & xml) 을 만든다 

    + compType : grid, chart, option 등 다양하게 존재할 수 있다 

// ComponentMapper.xml 에서 : 기존 소스는 typeAlias 사용하였음

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 

id : primary key

mysql -u user1 -p DBName

mysql> CREATE TABLE mc_component(

      id MEDIUMINT NOT NULL AUTO_INCREMENT,

      comp_type VARCHAR(5) NOT NULL,

      comp_cfg TEXT NOT NULL,

      PRIMARY KEY (id) 

    );

 -->

<mapper namespace="com.mobiconsoft.dashboard.mapper.ComponentMapper">

 

    <resultMap id="component" type="com.mobiconsoft.dashboard.domain.Component" >

        <result property="id" column="id"/>

        <result property="compType" column="comp_type"/>

        <result property="compCfg" column="comp_cfg"/>

    </resultMap>

 

    <select id="getComponents" resultType="com.mobiconsoft.dashboard.domain.Component">

        SELECT

        *

        FROM

        component

    </select>

 

    <select id="getComponent" parameterType="Integer" resultType="com.mobiconsoft.dashboard.domain.Component">

        SELECT

        *

        FROM

        component

        WHERE

        id=#{id}

    </select>

 

    <insert id="saveComponent" parameterType="com.mobiconsoft.dashboard.domain.Component"<!-- useGeneratedKeys="true" keyProperty="id"> -->

        INSERT INTO

        component(comp_type, comp_cfg)

        VALUES

        (#{compType}, #{compCfg})

    </insert>

    

    <update id="updateComponent" parameterType="com.mobiconsoft.dashboard.domain.Component"

        UPDATE 

        component

        SET

        comp_type=#{compType}, 

        comp_cfg=#{compCfg}

        WHERE

        id=#{id} 

    </update>

 

    <delete id="deleteComponent" parameterType="Integer">

        DELETE FROM

        component

        WHERE

        id=#{id}

    </delete>

 

</mapper>


// 서버 전체 파일 



3. UI-Router 사용하기

  - 관리 메뉴에서 Component를 선택하면 CRUD 할 수있는 메뉴 구조를 본문에서 가져야 한다

  - UI-Router를 이용하여 기존의 Routing 환경을 고도화한다 

// angular-route 라 반드시 설치되어 있어야 하고, 1.2.* 도 지원함 

// bower 설치 

bower install angular-ui-router --save

bower angular-ui-router#~0.2.0 install angular-ui-router#0.2.0

  - index.html에 route-segment 추가하기 

<!-- build:js scripts/angular-3.js -->

    <script src="bower_components/angular/angular.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>

    .. 중략 ..

<!-- endbuild -->

  - DashboardApp.js 메인 소스에 ui-router 모듈을 추가한다 (예제 참조)

var DashboardApp = angular.module('DasbhoardApp', [

  'ngRoute', 

  'ui.router',

  .. 중략 ..

  'DasbhoardApp.RestfulSvc'

]);

  - DashboardApp.js 의 config 에서 $routeProvider를 고도화한다 (소스 참조)

DashboardApp.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {


   $urlRouterProvider.otherwise("/main");

   $stateProvider

      .state('main', {

      url: '/main',

        templateUrl: 'views/main.html'

      })

      .state('resttest', {

      url: '/resttest',

        templateUrl: 'views/restTest.html',

        controller: 'RestTestBiz.personCtrl'

      })

      .state('jqgridtest', {

      url: '/jqgridtest',

        templateUrl: 'views/jqGridTest.html',

        controller: 'JqGridBiz.salesCtrl'

      })

      .state('mc-component', {

      url: '/mc-component',

        templateUrl: 'views/mc/component.html',

        controller: 'MCAdminCtrl.componentCtrl'

      });

  }]);


DashboardApp.run(['$rootScope', '$state', '$stateParams',

    function ($rootScope,   $state,   $stateParams) {


      // It's very handy to add references to $state and $stateParams to the $rootScope

      // so that you can access them from any scope within your applications.For example,

      // <li ng-class="{ active: $state.includes('contacts.list') }"> will set the <li>

      // to active whenever 'contacts.list' or one of its decendents is active.

      $rootScope.$state = $state;

      $rootScope.$stateParams = $stateParams;

  }]);

  - index.html 의 링크 내역 수정 (소스 참조)

   + ui-sref 애프리뷰트 directive 사용으로 routing 되는 정보를 현재 html 페이지의 ui-view 로 연결한다 

   + ui-view 애트리뷰트 directive 사용으로 화면이 나타나는 태그 영역

   + 메뉴 active는 $state.includes('<stateName>') 을 이용하여 현재의 state 이면 active로 표현 

  -즉 한페이지에서 ui-sref와 ui-view를 통해 확장해 가는 방식

<div class="collapse navbar-collapse">

  <ul class="nav navbar-nav">

    <li data-ng-class="{ active: $state.includes('main') }"><a ui-sref="main">Home</a></li>

    <li data-ng-class="{ active: $state.includes('resttest') }"><a ui-sref="resttest">RESTTest</a></li>

    <li data-ng-class="{ active: $state.includes('jqgridtest') }"><a ui-sref="jqgridtest">jqGridTest</a></li>

    

    <!-- <li data-ng-class="activeWhen(path()=='/')">

      <a href="" data-ng-click="setRoute('/')">Home</a>

    </li>

    <li data-ng-class="activeWhen(path()=='/resttest')">

      <a href="" data-ng-click="setRoute('/resttest')">RESTTest</a>

    </li>

    <li data-ng-class="activeWhen(path()=='/jqgridtest')">

      <a href="" data-ng-click="setRoute('/jqgridtest')">jqGridTest</a>

    </li> -->

    <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">Admin <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Project</li>

          <li><a href="">User</a></li>

          <li><a href="">Customer</a></li>

          <li><a href="">Code</a></li>

          <li><a href="">Role</a></li>

          <li class="divider"></li>

          <li class="dropdown-header">MobiconSoft</li>

          <!-- <li data-ng-class="activeWhen(path()=='/mc-component')">

      <a href="" data-ng-click="setRoute('/mc-component')">Component</a>

     </li> -->

     <li data-ng-class="{ active: $state.includes('mc-component') }"><a ui-sref="mc-component">Component</a></li>

          <li><a href="">Dashboard</a></li>

        </ul>

    </li>

  </ul>

</div>


<!-- Add your site or application content here -->

<!-- <div class="container" data-ng-view=""></div> -->

<div ui-view class="container" style="margin-top:30px"></div>



4. Component 관리를 위한 UI-Router 만들기

  - component 관리 화면 만들기

    + step-1 : component.html 으로 컴포넌트의 목록을 보여주고 컴포넌트를 선택하면 component.detail.html 이 우측에 표현된다

    + step-2 : component.read.html 로 컴포넌트 상세 정보를 읽거나 업데이트 또는 삭제한다  

    + step-3 : component.create.html 로 새로운 컴포넌트 정보를 저장한다 

    + step-4 : ComponentMod.js 개발하기 - CRUD Controller와 Service 그리고 index.html에 파일 추가하기 

    + step-5 : DashboardApp.js routing 정보 업데이트 및 ComponentMod.js 모듈 추가하기 

    

// index.html 에서 ui-view에 component.html전체가 표현됨

 <div ui-view class="container" style="margin-top:30px"></div>


// step-1,2,3) *.html 파일 만들기 

// component.html 에서 목록을 받아와서 ng-repeat 하기 

<li ng-repeat="component in components"

    ng-class="{ active: $state.includes('mc-component.detail') && $stateParams.componentId == component.id }">

  <a ui-sref=".detail({componentId:component.id})" style="padding: 2px 2px">{{component.name}}</a>

</li>

.. 중략 ..

// 여기에 component.detail.html 과 component.create.html 내용이 표현됨 

<div ui-view></div>


// component.detail.html : value에 값을 표현하기, ng-model을 통하여 controller에서 변경값 처리하기 (create html도 유사함) 

<form class="form-horizontal" role="form">

<div class="form-group">

<label class="col-sm-3 control-label"> Name</label>

<div class="col-sm-9">

<input type="text" class="form-control" placeholder="Name" ng-model="component.name" value="{{component.name}}" required>

</div>

</div>

<div class="form-group">

<label class="col-sm-3 control-label"> Type</label>

<div class="col-sm-9">

<input type="text" class="form-control" placeholder="Type" ng-model="component.type" value="{{component.type}}" required>

</div>

</div>

<div class="form-group">

<label class="col-sm-3 control-label"> Config</label>

<div class="col-sm-9">

<textarea class="form-control" id="message" name="message" ng-model="component.cfg" value="{{component.cfg}}"

placeholder="Config" rows="5" required></textarea>

</div>

</div>

<div class="form-group last">

<div class="col-sm-offset-3 col-sm-9">

<button type="submit" class="btn btn-success btn-primary" data-ng-click="update()">Update</button>

<button type="submit" class="btn btn-error btn-primary" data-ng-click="deleteComp()">Delete</button>

</div>

</div>

</form>

  - step-4 : CompoonentMod.js 개발 

'use strict';

// 모듈 정의 

var ComponentMod = angular.module('MobiConSoft.ComponentMod', []);

// ui-router의 서비스 추가 

ComponentMod.controller('ComponentMod.componentCtrl', ['$rootScope', '$scope', 'RestfulSvcApi', '$state', '$stateParams', 'componentSvc',

                                                              function ($rootScope, $scope, RestfulSvcApi, $state, $stateParams, componentSvc) {

        // 서브 화면에서 Create, Delete, Update가 일어나면 목록의 components 를 two-way binding으로 업데이트하기 위함

       $scope.$on("updateComponentList", function(event, data){

          console.log('--broadcasting data, ', data);

          $scope.components = data;

       });

var getAll = function() {

console.log('-----all: ', $scope.components);

componentSvc.getAll();

};

var getOne = function() {

console.log('-----one: ', $stateParams.componentId);

RestfulSvcApi.one({ domain: 'component', key: $stateParams.componentId },

function(response) {

console.log('componentCtrl : one data=',response);

   $scope.component = response;

},

function(response) {});

};

if($state.current.name === 'mc-component' || (!$stateParams.componentId && !$scope.components)) {

getAll();

};

if($state.current.name === 'mc-component.detail' && $stateParams.componentId) {

getOne();

};

$scope.update = function() {

console.log('componentCtrl : component=', $scope.component);

RestfulSvcApi.update({ domain: 'component'}, $scope.component,

function(response) {

console.log('---update, ', response);

getAll();

},

function(response) {});

};

$scope.deleteComp = function() {

RestfulSvcApi['delete']({ domain: 'component', key: $scope.component.id },

function(response) {

console.log('---delete, ', response);

$scope.component = {};

getAll();

},

function(response) {});

};

  

$scope.save = function() {

console.log('----> component is ', $scope.component);

RestfulSvcApi.save({ domain: 'component' }, $scope.component,

function(response) {

console.log('---save, ', response);

getAll();

},

function(response) {});

};

 }]);


// 전체 목록에 대해서만 Service로 구현함 : Broadcasting에 대한 별도 추가 테스트 필요함

ComponentMod.service('componentSvc', ['$rootScope', 'RestfulSvcApi', function ($rootScope, RestfulSvcApi) {

this.getAll = function() {

RestfulSvcApi.all({ domain: 'component' },

function(response) {

console.log('componentSvc : all data=', response);

$rootScope.$broadcast('updateComponentList', response);

},

function(response) {});

};

}]);

  - step-5 : DashboardApp.js routing status 업그레이드

'use strict';


var DashboardApp = angular.module('DasbhoardApp', [

  'ngRoute', 

  'ui.router',

  'ngAnimate',

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'DasbhoardApp.CommonCtrl',

  'MobiConSoft.ComponentMod',

  'DashboardApp.JqGridDrtv',

  'DasbhoardApp.RestTestBiz',

  'DasbhoardApp.JqGridBiz',

  'DasbhoardApp.RestfulSvc'

]);


DashboardApp.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) {


    $urlRouterProvider.otherwise("/main");

    $stateProvider

      .state('main', {

      url: '/main',

        templateUrl: 'views/main.html'

      })

      .state('resttest', {

      url: '/resttest',

        templateUrl: 'views/restTest.html',

        controller: 'RestTestBiz.personCtrl'

      })

      .state('jqgridtest', {

      url: '/jqgridtest',

        templateUrl: 'views/jqGridTest.html',

        controller: 'JqGridBiz.salesCtrl'

      })

      .state('mc-component', {

      url: '/mc-component',

        templateUrl: 'views/mc/component.html',

        controller: 'ComponentMod.componentCtrl'

      })

     .state('mc-component.detail', {

      url: '/detail/{componentId:[0-9]{1,4}}',

       templateUrl: 'views/mc/component.detail.html',

       controller: 'ComponentMod.componentCtrl'

     })

     .state('mc-component.create', {

      url: '/create',

       templateUrl: 'views/mc/component.create.html',

       controller: 'ComponentMod.componentCtrl'

     });

  }]);


DashboardApp.run(['$rootScope', '$state', '$stateParams',

    function ($rootScope,   $state,   $stateParams) {

      $rootScope.$state = $state;

      $rootScope.$stateParams = $stateParams;

  }]);

  - 결과화면 

    


* 소스 : https://github.com/ysyun/SPA_Angular_SpringFramework_Eclipse_ENV/tree/feature_admin_routing



<참조>

  - AngularJS Views and Directives

  - Angular Route Segment

  - UI-Route : 예제 소스

posted by Peter Note
2013. 11. 30. 11:58 HTML5, CSS3/jQuery

페이스북 같은 앱에서 사용하는 좌/우측 스라이딩 사이드 메뉴를 Angular.js 기반으로 만들어진 컴포넌트에 대해 알아보자. 



Angular-Snap.js

    

  - http://jtrussell.github.io/angular-snap.js/

  - 드래그하여 메뉴를 열수 있다 

  - 버튼 클릭으로 메뉴를 열수 있다 

  - IE8 지원하나 snap.js 가 IE8 미지원이므로 미지원



Angular-SlideNav

  

  - https://github.com/sthomp/angular-slidenav.js

  - 버튼 클릭으로 메뉴를 열수 있다 

  - IE8 미지원



jPanelMenu

  

  - http://jpanelmenu.com/

  - jQuery 기반 메뉴

  - IE8 ?



Sidr

  

   - http://www.berriart.com/sidr/

   - left, right 슬라이딩 메뉴 

   - IE8 지원



Nexus Style Side

  

  - http://www.jqueryscript.net/menu/Google-Nexus-Page-Like-Sidebar-Menu-with-CSS3-javascript.html

  - 작은 아이콘에서 큰 아이콘과 텍스트가 펼치는 효과 (강추)

  - IE8 미지원



Multi-Level Side Menu

  

  - http://www.jqueryscript.net/menu/Multi-Level-Slide-Push-Menu-with-CSS3-Javascript-MultiLevelPushMenu.html

  - 또 다른 멀티 레벨 메뉴 (강추) : 메뉴로 사용하기 괜찮음

  - IE8 미지원

 


Blueprint Side

  

  - http://tympanus.net/Blueprints/SlidePushMenus/

  - 왼쪽, 오른쪽, 위, 아래로 메뉴가 펼쳐진다

  - 스타일은 푸른색  

  - IE8 지원 



MMenu

  

  - http://mmenu.frebsite.nl/

  - 또 다른 jQuery MMenu (강추)

  - 모바일에 잘 맞는 슬라이딩 메뉴

  - 다른 컴포넌트도 있음 

  - IE8 미지원



Tegansnyder

  

  - https://github.com/tegansnyder/JQuery-Mobile-Slide-Menu

  - jQuery Mobile Slide Menu : 페이스북 형태 

  - 페이스북과 유사한 Slide Menu 만들기 블로그 : 이것이 좀 더 괜찮음

  - IE8 지원



Snap.js

  

  - https://github.com/jakiestfu/Snap.js

  - left, right 메뉴 제공

  - 드래그 제공

  - 페이스북 스타일 제공

  - IE8, 9 미지원 



Meny

  - http://lab.hakim.se/meny/

  - IE8 지원



<참조> 

  - 원문 : Slide Out Sidebar Navigations

  - 원문 : jQuery Side Menu 목록

  - Angular Snap.js

  - Angular SlideNav

  - Angular Datatable

  - 15개의 RWD Menu 

posted by Peter Note
2013. 11. 28. 13:19 AngularJS/Concept

하기 영상을 보면 JSON Object일 때와 String(Primitive type) 일때의 처리가 틀리다 왜그럴까 고민을 해보자 



1. Angular.js scope 상속 방법

  - childe scope는 기본적으로 parent scope를 상속받는다. 

  - 단, directive에서 scope : {...} 설정에서 scope isolate을 정하게 되어있다 

  - 원칙

1) define objects in the parent for your model, then reference a property of that object in the child: parentObj.someProp

    부모 scope에 object를 정의하면 child scope로 상속 된다. 그러나 primitive type(string, number, boolean)은 상속되지 않는다 

2) use $parent.parentScopeProperty (not always possible, but easier than 1. where possible)

    $parent 의 프로퍼티를 사용한다. 항상 가능하진 않으나 1)번 보다는 쉽다

3) define a function on the parent scope, and call it from the child (not always possible)

    부모 scope에 function을 정의하면 child scope로 상속 된다



2. JavaScript Prototypal 상속에 대하여

  - scope chaing에 따른 상속 : childScope는 primitive, object 전부 호출 가능 




3. Angular.js Scope 상속에 대하여 

  - 상속유형 

1) The following create new scopes, and inherit prototypically: ng-repeat, ng-include, ng-switch, ng-view, ng-controller, directive with scope: true, directive with transclude: true.

    ng-repeat, ng-include, ng-switch는 새로운 scope를 만들고 prototypically를 상속을 한다. 

    즉, scope:true, transclude:true인 directive이다 (새로운 scope를 만들지 않는 것은 scope: false 이다)

2) The following creates a new scope which does not inherit prototypically: directive with scope: { ... }. This creates an "isolate" scope instead.

     prototypically사용을 하지 않고 새로운 scope를 만들려면 scope: {...} 를 정의하여 "isolate" scope를 만들어야 한다


  - ng-include의 예

// controller 에서 

$scope.myPrimitive = 50;

$scope.myObject    = {aNumber: 11}; 


// html 에서 

<script type="text/ng-template" id="/tpl1.html">

    <input ng-model="myPrimitive">

</script>

<div ng-include src="'/tpl1.html'"></div>


<script type="text/ng-template" id="/tpl2.html">

    <input ng-model="myObject.aNumber">

</script>

<div ng-include src="'/tpl2.html'"></div>

  - 최초 설정 상태 : ng-include는 새로운 ChildScope1, 2를 생성한다. ParentScope는 ng-include를 감싸는 상위 Controller scope 가정 


  - 파란색 input box에 값을 입력 할 때 : primitive는 child scope에 새로운 property가 생성됨



  - 분홍색 input box에 값을 입력 할 때 : object는 parentScope의 값이 변경됨 


  - 파란색을 분홍색처럼 영향을 미치고 싶다면 하기와 같이 사용하면 parent scope의 primitive 값을 child scope에서 제어가능

     * 펑션도 Object와 동일하게 Child에 상속된다 

<input ng-model="$parent.myPrimitive">

  - ng-switch 도 ng-include와 유사하게 동작한다 



4. ng-repeat 상속의 경우 

  - ng-include나 ng-switch와 약간 틀리다 

// controller 에서 

$scope.myArrayOfPrimitives = [ 11, 22 ];

$scope.myArrayOfObjects    = [{num: 101}, {num: 202}]


// html 에서 

<ul><li ng-repeat="num in myArrayOfPrimitives">

       <input ng-model="num">

    </li>

<ul>


<ul><li ng-repeat="obj in myArrayOfObjects">

       <input ng-model="obj.num">

    </li>

<ul> 

  - 최초 ng-repeat이 새로운 scope를 하나 만들고 iteration 하면서 아이템의 값을 새로운 scope를 또 만들어 새로운 property 에 할당함

    여기서, 새로운 proeperty란 loop 변수 - 위의 예에서 num 또는 obj - 이다. 즉, 하기와 같은 작업이 일어나는 것이다. 

childScope = scope.$new(); // child scope prototypically inherits from parent scope ...     

childScope[valueIdent] = value; // creates a new childScope property

  - 파란색 Primitive 배열 경우 : 새로운 scope가 생기고 loop 변수 num이 Childe scope에 생기고 primitive 값의 복사가 이루어진다. 

    즉, referencing 되지 않아 Parent Scope와 관련성이 없어진다


  - 분홍색 Object 배열 경우 : Child Scope가 생성되고 loop 변수 obj는 parent scope의 배열 요소를 referencing 한다. 

    즉, child에서 변경을 하면 parent scope 값도 변경되는 것이다 


  - ng-controller

    + 일반적인 prototypal 상속을 따른다 

    + controller 끼리 $scope를 통해 정보를 공유하는 것은 좋지 않다. 공유할려면 service를 이용한다 (참조)

  - ng-view

    + ng-include와 동일하다 



5. 사용자 정의 Directive의 Scope 경우

  - directives를 만들 때

    + scope: false 는 기본값이다. directive를 만들면 scope를 새로 생성하지 않는다 

    + scope: true 를 정의하면 일반적인 prototypal 상속을 따른다

    + scope: { ... } 경우는 isolate/isolated scope를 새롭게 생성한다.

  - scope: {...} 경우 상세 고찰 

1) prototypically 상속을 하지 않는다 

2) 재사용 가능한 컴포넌트를 만들 때 사용한다. 즉 컴포넌트가 parent scope의 값을 read/write 못하게 한다

3) 그러나 간혹 parent scope에 접근(access) 하고 싶을 경우 object hash를 사용한다 

    '=' : two way binding (isolate scope <-> parent scope) 

    '@' : one way binding (isolate scope <- parent scope)

    '&' : parent scope expressions 에 바인딩 된다 

4) objec hash는 자신의 directive의 attributes 가 바인딩시에 이용됨을 주의한다 

5) object hash 는 parent scope의 property를 지정 할 수 없다 

    즉, 'parentProp' 가 있을 때 <div my-directives>의 scope: {localProp: '@parentProp'} 지정로 하지 않고, 대신 접근하고 싶다면

    <div my-directives the-Parent-Prop=parentProp>  의 scope: {localProp: '@theParentProp'}로 parent property를 명시해야 한다 (참조 예제)

6) isolate scope 의 attribute 명칭과 parent scope의 property 명칭이 같다면 scope: { attributeName: '=' } 이런식으로 설정함

    틀리면 scope: { keyName1: '@isolateAttributeName', keyName2: '=isolateAttributeName'} 으로 정의함

  - isolate scope 개념도

    + isolate scope 가 생성되면 __proto__ 는 Scope를 레퍼런스 한다 (하기 그림의 오랜지색 박스 'Object')

    + isolate scope의 $parent는 parent scope를 레퍼런스 한다. 그렇다고 prototypically 상속을 parent scope로 부터 하진 않는다 

    + 하기와 같이 정의 하였을 경우 

// html 에서 : attribute 에서 parent scope의 property를 접근한다 

<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">


// directive 에서 : =, @, & 등을 사용하여 parent scope property의 접근 방법을 명시한다 

scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' } 


// directive의 linking function 에서 

scope.someIsolateProp = "I'm isolated" 

    + 위 코드이 개념도 


 - @ 을 사용하면 linking function안에 하기 코드를 사용한다. 

    즉, attrs.$observe('interpolated', function(value) { ... } 의 value에 11을 설정한다  

attrs.$observe('attr_name', function(value) { ... }

  - 따라서 scope.interpolatedProp는 linking function 안에 정의 되지 않으나, 

     scope.twowayBindingProp는 inking function 안의 정의 된다. 이에 대해 해당 링크를 참조하자 (예제)

 


6. Transclude 에 대하여

  - transclude :true 하면 새로운 "transcluded" child scope를 생성한다. 이는 일반적인 prototypically 상속을 한다 

  - transaclude content가 two way binding을 원한다면 $parent를 이용한다

  - isolate scope 객체와 transclude scope 객체는 형제지간으로 $parent는 같은 부모 object를 레퍼런스 한다 (sibling)

  - isolate scope 의 $$nextSibling은 transclude scope 객체를 레퍼런스 한다 



  - 링크 사이트를 참조한다


즉, Isolate Scope를 가지게 되면 일반적인 Prototypal 상속을 따르지 않는다. 그러나 Isolate Scope(== Child Scope)가 간혹 parent scope를 접근하고 싶을 때는 @, =, & 와 같은 object hash를 Directive의 Attribute 앞에 붙임으로써 접근 방법을 지정할 수 있다. 


* 주의) 버전 1.1.* 와 1.2.* 버전사이의 동작이 틀리다. 1.1.* 에서는 정상 작동하지만 1.2.* 에서 Isolate Scope방식이 정상 동작하지 않는다! (이유를 찾아봐야 함)

  예제에서 왼쪽 메뉴의 "External Resource"를 바꾸어 가며 테스트해 보라

  "http://code.angularjs.org/1.1.5/angular.js" <->  "http://code.angularjs.org/1.2.0/angular.js"



<참조>

  - 원문 : Angular.js Scope에 대한 이해 개념도

  - Angular.js Scope 상속 영역 

  - Angular.js Architecture 고려 사항 (필독)

  - Angular.js Isolated Scope에 대한 이해 (필독)

  - Isolated Scope @, =, & 예제

  - Transclude의 two-way binding 실현

  - 본문 다이어그램 소스보기 프로그램

posted by Peter Note