-
완성 - 메뉴 구현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