+2

[Declarative Programming + Elm] Bài 15 - Navigation & URL Parsing

Trong bài viết này, chúng ta sẽ xem xét một ví dụ đơn giản mà Elm đưa ra về việc xử lý yêu cầu điều hướng giữa các trang nội dung đơn khi người dùng nhấn vào một liên kết bất kỳ trong SPA. Để thực hiện ví dụ này, chúng ta sẽ cần cài đặt thêm một package hỗ trợ.

cd Documents && cd learn-elm
elm install elm/url
elm reactor

Navigation

Đầu tiên vẫn là chương trình main với đầy đủ các yếu tố kiến trúc theo yêu cầu tham số đầu vào của Browser.application.

module Main exposing (main)

import Browser exposing (..)
import Browser.Navigation as Navigation exposing (Key)
import Html exposing (..)
import Html.Attributes as Attributes exposing (..)
import Url exposing (..)

-- main - - - - - - - - - - - - - - - - - - - - - - - - - - -

type alias SPA =
   { init : () -> Url -> Key -> ( Model, Cmd Msg )
   , view : Model -> Document Msg
   , update : Msg -> Model -> ( Model, Cmd Msg )
   , subscriptions : Model -> Sub Msg
   , onUrlRequest : UrlRequest -> Msg
   , onUrlChange : Url -> Msg
   }

main : Program () Model Msg
main = Browser.application
   ( SPA init view update subscriptions onUrlRequest onUrlChange )

Chúng ta sẽ có một kiến trúc HTML đơn giản bao gồm một danh sách <ul> các liên kết điều hướng và một <div> hiển thị nội dung của trang đơn tương ứng với mỗi liên kết. Nội dung hiển thị ở đây sẽ chỉ đơn giản là đường dẫn đầy đủ của trang đơn cần hiển thị. Do đó nên initview cũng khá đơn giản.

-- init - - - - - - - - - - - - - - - - - - - - - - - - - - -

type alias Model =
   { key : Key
   , url : Url
   }

init : () -> Url -> Key -> ( Model, Cmd Msg )
init _ url key = ( Model key url, Cmd.none )

-- view - - - - - - - - - - - - - - - - - - - - - - - - - - -

view : Model -> Document Msg
view model =
   { title = "URL & Navigation"
   , body = bodyHtml ( model )
   }

bodyHtml : Html Msg
bodyHtml model =
   [ -- body
      ul [] [
         li [] [ link "/home" ],
         li [] [ link "/profile" ],
         li [] [ link "/reviews/the-century-of-the-self" ],
         li [] [ link "/reviews/public-opinion" ],
         li [] [ link "/reviews/shah-of-shahs" ]
      ], -- ul
      
      div [] [
         h1 [] [ text ( Url.toString model.url ) ]
      ] -- div
   ] -- body

link : String -> Html msg
link path = a [ href path ] [ text path ]

Ở đây cả initview đều không có điểm nào gửi Msg tới trình điều khiển chính Program. Tuy nhiên có một yếu tố mới đó là kiểu Key được sử dụng tại init. Giá trị này được sử dụng để đảm bảo các sub-program của module Navigation bao gồm pushUrl, replaceUrl, back, và forward sẽ chỉ có hiệu lực nếu được cung cấp đúng key đã khởi tạo khi trang web mới được tải về trình duyệt.

Các trường hợp xử lý tại trình update lúc này sẽ bao gồm:

  • Người dùng nhập địa chỉ mới vào thanh địa chỉ Address Bar của trình duyệt web hoặc khi địa chỉ được thay đổi khi người dùng click vào liên kết nào đó.
  • Người dùng click chuột vào một liên kết trong trang web (xử lý trước khi địa chỉ thay đổi):
    • Liên kết trỏ tới một trang đơn cùng hệ thống
    • Liên kết trỏ tới một trang web khác
-- update - - - - - - - - - - - - - - - - - - - - - - - - - - -

type Msg = LinkClicked UrlRequest
         | UrlChanged Url

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = case ( msg ) of
   --
   LinkClicked urlRequest -> case ( urlRequest ) of
      Browser.Internal url ->
         let cmd = Navigation.pushUrl ( model.key ) ( Url.toString url )
         in  ( model, cmd )
      Browser.External href ->
         let cmd = Navigation.load ( href )
         in  ( model, cmd )
   --
   UrlChanged newURL ->
      let updatedModel = { model | url = newURL }
      in  ( updatedModel, Cmd.none )

Và các yếu tố kiến trúc còn lại...

-- subscriptions - - - - - - - - - - - - - - - - - - - - - - - - - - -

subscriptions : Model -> Sub Msg
subscriptions _ = Sub.none

-- onUrlRequest - - - - - - - - - - - - - - - - - - - - - - - - - - -

onUrlRequest : UrlRequest -> Msg
onUrlRequest req = LinkClicked ( req )

-- onUrlChange - - - - - - - - - - - - - - - - - - - - - - - - - - -

onUrlChange : Url -> Msg
onUrlChange url= UrlChanged ( url )

Logic điều hướng mà chúng ta có ở đây xuất phát từ lần truy cập đầu tiên khi người dùng nhập địa Url vào thanh địa chỉ của trình duyệt web và mở trang web lần đầu hoặc từ một liên kết Google tìm kiếm hay bất kỳ website nào khác. Lúc này init sẽ truy xuất chuỗi mô tả Url và gửi cho trình điều khiển để truy vấn dữ liệu từ server và hiển thị nội dung tương ứng.

http://localhost:8000/src/Main.elm

Sau đó thì mỗi khi người dùng nhấn vào một liên kết nào đó trong trang, ví dụ như <a href="/home"> thì trình onUrlRequest sẽ được kích hoạt và gửi tới trình điều khiển một tin nhắn LinkClicked UrlRequest và sau đó Msg này sẽ được chuyển cho trình update để cập nhật nội dung của trang web mà không cần tải lại toàn bộ trang.

Tuy nhiên lúc này model vẫn chưa được cập nhật ngay để lưu trữ Url mà người dùng nhấn vào. Và khi Url của trình duyệt thay đổi thì trình onUrlChange mới được kích hoạt để gửi đi tin nhắn UrlChanged : Msg để chúng ta có thêm lựa chọn bổ sung logic xử lý và cập nhật model.

URL Parser

Trên thực tế thì sau thao tác điều hướng căn bản, chúng ta sẽ phải tìm hiểu thêm cách thức để phân tích đường dẫn yêu cầu Request Url trong trường hợp người dùng nhấn vào một liên kết trỏ tới một trang đơn cùng hệ thống.

Tuy nhiên việc tìm hiểu các công cụ xử lý nhóm tác vụ này sẽ yêu cầu sử dụng các sub-program được gọi là Higher Order Functions mà mình đã xác định là để dành cho Sub-Series tiếp theo. Và vì vậy nên câu chuyện SPA của chúng ta sẽ cần lui lại một chút cho đến khi Higher Order Functions và một số khái niệm liên quan được giới thiệu xong ở Sub-Series mới.

[Functional Programming + Elm] Bài 1 - Functional Aspects


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.