Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Simply Lift : Chapter 13 - From MVC

원문 : http://simply.liftweb.net/index-Chapter-13.html#toc-Chapter-13

13장
From MVC


여러분이 MVC세상의 왔고 라우트를 정의하고 컨트롤러와 뷰를 정의하는데 익숙합니다.

Lift는 다릅니다. HTML요청을 위해서 Lift는 뷰를 먼저 로드하고 뷰에서부터 여러분의 페이지를 만듭니다. 또한 HTML이 아닌 데이터를 위해서 REST스타일의 요청을 지원합니다.(11장 참고)

“왜?” 복잡한 HTML페이지는 드물게 로직의 주요한 부분을 담고 있기 때문입니다... 단일 컨트롤러.... 하지만 많은 여러가지 컴포넌트들을 담고 있습니다. 이러한 컴포넌트중의 일부는 상호작용하고 일부는 그렇지 않습니다. Lift에서 뷰에서 HTML 결과페이지를 렌더링하기 위해서 컴포넌트의 컬렉션을 정의합니다.

그래서 동적인 컨텐츠를 가지는 페이지를 만들기 위해서 3가지가 필요합니다:

  • 페이지를 위해서 SiteMap 엔트리를 만듭니다.
  • 뷰(HTML)를 생성합니다.
  • 동작(behavior)를 생성합니다.(들어오는 HTML을 동적으로 생성된 HTML로 변환하는 스니펫)
이 프로젝트의 소스는 https://github.com/dpp/simply_lift/tree/master/samples/from_mvc에서 볼 수 있습니다.



13.1 중요한 것을 먼저 합니다(First things first)

Lift를 사용하는 첫번째 단계는 Java 1.6이상을 여러분의 머신에 설치하는 것입니다. tar나 zip이 필요할 것입니다.

Lift 템플릿의 TAR이나 Zip버전을 다운로드하고 압축을 풉니다.

first_lift라는 또다른 디렉토리안으로 lift_basic 프로젝트를 복사합니다.

first_lift로 들어가서 sbt를 입력합니다. sbt, Simple Build Tool가 모든 의존성을 다운로드하는데는 몇분정도 걸릴 것입니다. > 프롬프트에서 update를 입력하면 Lift를 다운로드받을 것 이고 이것인 시작하는데 필요한 모든 것입니다. 모든 것이 다운로드된 다음에는 jetty-run을 입력하고 웹브라우저에서 http://localhost:8080로 접속하면 동작하는 애플리케이션을 볼 수 있을 것입니다. 계속해서 돌아가는 애플리케이션을 수정한 코드로 업데이트하기 위해서 sbt 프롬프트에서 ~prepare-webapp을 입력합니다.



13.2 SiteMap 엔크리 생성

사이트에서 모든 페이지는 SiteMap 엔트리가 필요합니다.(3.2장과 7.5장 참고)

Boot.Scala파일(src/main/scala/bootstrap/liftweb/Boot.scala)를 열고 SiteMap 정의를 갱신합니다.

// SiteMap 만듭니다
def sitemap(): SiteMap = SiteMap(
   Menu("Home") / "index",
   Menu("Second Page") / "second"
)



13.3 뷰 생성

다음은 SiteMap에서 정의된 경로에 해당하는 파일을 생성해야 합니다. 그래서 src/main/webapp/index.html 파일을 보겠습니다.

리스팅 13.1: index.html

<!DOCTYPE html>
<html>
   <head>
      <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
      <title>Home</title>
   </head>
   <body class="lift:content_id=main">
      <div id="main" class="lift:surround?with=default&at=content">
         <div>
            Hi, I'm a page that contains the time:
            <span class="lift:TimeNow">??? some time</span>.
         </div>

         <div>
            And a button: <button class="lift:ClickMe">Click Me</button>.
         </div>
      </div>
   </body>
</html>

페이지는 유효한 HTML5입니다. <body class="lift:content_id=main">는 “이 페이지에서 id가 ‘main’인 엘리먼트를 제외하고는 모두 무시하라”고 알려줍니다.

<div id="main" class="lift:surround?with=default&at=content">는 이 엘리먼트 주위의 기본 페이지 크롬(chrome)을 감싸라”고 알려줍니다.

<span class="lift:TimeNow">??? some time</span>는 “TimeNow 스니펫을 찾고 그 스니펫이 담고 있는 규칙으로 이 엘리먼트를 변환하라”고 알려줍니다. 7.1을 참고하세요 결과는 <span>Fri Jan 21 11:30:34 PST 2011</span>가 될 것입니다.

그래서 아주 간단합니다. Lift에서 어떤 스니펫이 정적인 컨텐츠를 동적인 컨텐츠로 변환하는데 사용되는지 말해주면 됩니다.



13.4 스니펫 생성

다음은 동적인 규칙들에 기반해서 템플릿의 섹션을 변환하기 위한 규칙들이 어떤 것인지 Lift에게 말해줘야 합니다. 이것이 스니펫입니다... NodeSeq => NodeSeq 를 변환하는 함수입니다. TimeNow 스니펫을 보겠습니다:

리스팅 13.2: TimeNow.scala

// Lift가 스니펫을 찾을 수 있도록
// snippet 팩키지이어야 합니다.
package code
package snippet

// 몇가지 인풋들
import net.liftweb._
import util._
import Helpers._

// 우리의 스니펫
object TimeNow {
   // 현재시간을 들어오는 엘리먼트의 바디안에 두는
   // 함수 (NodeSeq => NodeSeq)를 생성합니다
   def render = "* *" #> now.toString
}

관례에 따라 Lift가 어떻게 찾는지 알고 있기 때문에 이 스니펫은 snippet 팩키지안에 있어야 합니다.

스니펫은 상태를 가지지 않기 때문에 싱글톤인 object입니다.

스니펫을 호출했을 때 다른 메서드를 명시하지 않고도 Lift는 스니펫의 render 메서드를 호출합니다.

스니펫은 현재 시간을 모든 HTML 엘리먼트의 바디안으로 인서트하기 위해서 Lift의 CSS Selector Transform을 사용하는 함수, NodeSeq => NodeSeq를 생성합니다: def render = "* *" #> now.toString



13.5 Ajax스럽게 하기

ClickMe 스니펫은 약간 더 복잡하지만 이것은 페이지에서 특정 컴포넌트가 우세하지 않은 Lift의 View First의 강력함, 특히 “Second Page”,을 보여줍니다. 여기 ClickMe코드가 있습니다:

리스팅 13.3: ClickMe.scala

// Lift가 스니펫을 찾을 수 있도록
// snippet 팩키지이어야 합니다
package code
package snippet

// 몇몇 인풋
import net.liftweb._
import util._
import Helpers._
import http._
import js.JsCmds._

// 우리의 스니펫
object ClickMe {
   // 요청에 연관된 변수들
   private object pos extends RequestVar(0)
   private object cnt extends RequestVar(0)

   // 함수(NodeSeq => NodeSeq)를 생성합니다
   // 버튼에 onClick 메서드를 설정합니다
   def render = {
      // 페이지에서 위치(position)을 캡쳐합니다
      val posOnPage = pos.set(pos.is + 1)

      "button [onclick]" #>
      SHtml.ajaxInvoke(() => {
         cnt.set(cnt.is + 1) // 클릭수를 증가시킵니다
         Alert("Thanks pos: "+posOnPage+ " click count "+cnt)
      })
   }
}

요청범위(request-scoped)의 값을 유지하는 2개의 RequestVars를 정의합니다. Lift에서 요청의 범위는 이 페이지에 연관된 모든 Ajax 요청들을 추가해서 로드하는 최초의 전체 HTML페이지입니다.

스니펫의 render 메서드가 호출되었을 때 pos RequestVar를 위한 현재 값을 획득합니다.

스니펫은 버튼의 onclick 메서드의 Ajax call의 호출과 결합됩니다. 버튼이 클릭되었을 때 함수는 호출됩니다.

함는 페이지의 버튼 포지션의 범위를 넘어 닫힙니다. 버튼들은 모두 cnt RequestVar를 공유하고 단일 페이지 로딩을 위해서 버튼이 눌려진 수가 카운트됩니다. 같은 페이지를 5개의 다른 브라우저탭에서 열었을 때 각 탭은 각각의 페이지 카운트를 가질 것입니다.

이것은 Lift의 컴포넌트의 특징을 보여주고 페이지에서 복잡한 아이템들을 갖는 것이 왜  front-controller를 갖는 것이 아니라 많은 HTML엘리먼트들과 연관된 많은 동작들을 갖는다는 것을 의미하는지 보여줍니다.



13.6 다음 단계

Lift의 매력전인 Ajax와 Commet에 대해서 더 보기를 원한다면 2장을 확인하세요. SiteMap과 스니펫의 기본적인 부분에 대해서 더 알고 싶다면 3장을 참고하세요. Lift가 어떻게 form을 다루는지 보려면 4장을 참고하세요.
2011/03/12 02:54 2011/03/12 02:54