ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 완성 - 메뉴 구현
    web/laminar 2022. 1. 29. 10:30

    문제

    Waypoint 를 이용해 Router 적용 코드에 fomantic-ui menu 를 적용해 본다.

     

    http://localhost:9000/search?engine=google&query=bts 입력 했을 때

    http://localhost:9000/login 입력 했을 때

    코드

    import com.raquo.laminar.api.L
    import com.raquo.laminar.api.L._
    import com.raquo.laminar.nodes.ReactiveHtmlElement
    import com.raquo.waypoint._
    import org.scalajs.dom
    import org.scalajs.dom.html
    import upickle.default._
    
    class Radio(name: String, items: Seq[String]) {
      val selectedVar: Var[String]            = Var("")
      val elem: ReactiveHtmlElement[html.Div] = div(
        cls := "ui form",
        div(
          cls := "inline fields",
          label(name),
          items.map(item =>
            div(
              cls := "field",
              div(
                cls := "ui radio checkbox",
                input(
                  typ := "radio",
                  checked <-- selectedVar.signal.map(_ == item),
                  onChange.mapTo(item) --> selectedVar
                ),
                label(item)
              )
            )
          )
        )
      )
    }
    
    class TextInput(name: String, description: String) {
      val textVar: Var[String]                = Var("")
      val elem: ReactiveHtmlElement[html.Div] = div(
        label(name),
        cls := "ui input",
        input(
          typ := "text",
          onMountFocus,
          value <-- textVar,
          onInput.mapToValue --> textVar,
          placeholder := description
        )
      )
    }
    
    object Main {
      sealed trait Page
      case class SearchPage(engine: String, query: Option[String]) extends Page
      case object LoginPage                                        extends Page
    
      implicit val SearchPageRW: ReadWriter[SearchPage] = macroRW
      implicit val rw: ReadWriter[Page]                 = macroRW
    
      def main(args: Array[String]): Unit = {
        object Routes {
          // path 까지 route 로 사용될 경우 Route.withQuery 사용
          private val searchRoute: Route[SearchPage, (String, Option[String])] =
            Route.onlyQuery[SearchPage, (String, Option[String])](
              encode = searchPage => (searchPage.engine, searchPage.query),
              decode = args => SearchPage(args._1, args._2),
              pattern = (root / "search" / endOfSegments) ? (param[String]("engine") & param[String]("query").?)
            )
    
          private val loginRoute: Route[LoginPage.type, Unit] = Route.static(LoginPage, root / "login" / endOfSegments)
    
          private val router = new Router[Page](
            routes = List(searchRoute, loginRoute),
            getPageTitle = (page: Page) => "my title",
            serializePage = page => write(page)(rw),
            deserializePage = pageStr => read(pageStr)(rw)
          )(
            $popStateEvent = L.windowEvents.onPopState,
            owner = L.unsafeWindowOwner
          )
    
          private val splitter: SplitRender[Page, HtmlElement] = SplitRender[Page, HtmlElement](router.$currentPage)
            .collectSignal[SearchPage]($searchPage => SearchPageView.render($searchPage))
            .collectStatic(LoginPage)(LoginPageView.render)
    
          def pushState(page: Page): Unit = router.pushState(page)
    
          def replaceState(page: Page): Unit = router.replaceState(page)
    
          def absoluteUrlForPage(page: Page): String = router.absoluteUrlForPage(page)
    
          val view: Signal[HtmlElement] = splitter.$view
        }
    
        object SearchPageView {
          val radio     = new Radio("Search Engine", List("naver", "google", "yahoo"))
          val textInput = new TextInput("Search Query: ", "Enter query")
    
          // 주의: val 이 아니라 def 로 선언되어야 함
          def searchPage: SearchPage =
            SearchPage(SearchPageView.radio.selectedVar.now(), Some(SearchPageView.textInput.textVar.now()))
    
          def render($searchPage: Signal[SearchPage]): Div =
            div(
              h3("Search Page"),
              $searchPage.map(_.engine) --> radio.selectedVar,
              radio.elem,
              $searchPage.map(_.query.getOrElse("")) --> textInput.textVar,
              textInput.elem,
              br(),
              button(
                "Submit",
                onClick --> (_ => Routes.pushState(searchPage))
              ),
              div(
                hr(),
                div(
                  "selected engine: ",
                  child.text <-- $searchPage.map(_.engine)
                ),
                div(
                  "query: ",
                  child.text <-- $searchPage.map(_.query.getOrElse(""))
                )
              )
            )
        }
    
        object LoginPageView {
          def render: Div =
            div(
              h3("Login Page")
            )
        }
    
        val selectedMenuVar: Var[String] = Var("")
        val content: Div                 =
          div(
            div(
              cls := "ui inverted menu",
              a(
                cls := "item",
                cls <-- selectedMenuVar.signal.map(x => if (x == "Login") "active" else ""),
                "Login",
                onClick.preventDefault --> { _ =>
                  selectedMenuVar.set("Login")
                  Routes.pushState(LoginPage)
                },
                href := Routes.absoluteUrlForPage(LoginPage)
              ),
              a(
                cls := "item",
                cls <-- selectedMenuVar.signal.map(x => if (x == "Search") "active" else ""),
                "Search",
                onClick.preventDefault --> { _ =>
                  selectedMenuVar.set("Search")
                  Routes.pushState(SearchPageView.searchPage)
                },
                href := Routes.absoluteUrlForPage(SearchPageView.searchPage)
              )
            ),
            child <-- Routes.view
          )
    
        val containerNode = dom.document.getElementById("main_content")
        render(containerNode, content)
      }
    }

    보완할 부분

    • selectedMenuVar 가 pushState 에 영향을 받지 않으므로, URL 입력으로 직접 접근했을 때 메뉴 탭의 active 가 설정되지 않는다.
    • Radio 나 TextInput 이 onChange, onInput 이벤트에 대해 selectedVar, textVar 를 변경하도록 적용되었기 때문에 Search 탭에서 submit 버튼을 누르지 않은 상태로 Login 탭으로 이동후 다시 Search 탭으로 돌아오면 submit 버튼을 누른 것과 같은 결과를 보게된다.

    'web > laminar' 카테고리의 다른 글

    EventStream 의 flatMap vs combineWithFn  (0) 2022.10.01
    Waypoint 를 이용해 Router 적용  (0) 2022.01.28
    children 으로 List 표현하기  (0) 2022.01.20
    Observer 를 사용해 side effect 처리하기  (0) 2022.01.09
    Component - TextInput  (0) 2022.01.09

    댓글

Designed by Tistory.