URLのパース
実際のウェブアプリケーションでは、異なるURLごとに異なる内容を表示したいでしょう。
- /search
- /search?q=seiza
- /settings
これはどのようにすればいいのでしょうか? ここではelm/urlを使って、生の文字列をElmの素敵なデータ構造へとパースしていきます。例を見ていくだけで、このパッケージが何より役に立つことがわかると思います。私たちがやろうとしていることが、まさにそれですから!
例 1
美術のウェブサイトがあり、次のようなURLのページがあるとしましょう。
- /topic/architecture
- /topic/painting
- /topic/sculpture
- /blog/42
- /blog/123
- /blog/451
- /user/tom
- /user/sue
- /user/sue/comment/11
- /user/sue/comment/51
トピックのページ、ブログの投稿、ユーザ情報のページがあり、それぞれのユーザのコメントを見ることもできます。Url.Parser モジュールを使って、次のようにURLパーサを書くといいでしょう。
import Url.Parser exposing (Parser, (</>), int, map, oneOf, s, string)
type Route
  = Topic String
  | Blog Int
  | User String
  | Comment String Int
routeParser : Parser (Route -> a) a
routeParser =
  oneOf
    [ map Topic   (s "topic" </> string)
    , map Blog    (s "blog" </> int)
    , map User    (s "user" </> string)
    , map Comment (s "user" </> string </> s "comment" </> int)
    ]
-- /topic/pottery        ==>  Just (Topic "pottery")
-- /topic/collage        ==>  Just (Topic "collage")
-- /topic/               ==>  Nothing
-- /blog/42              ==>  Just (Blog 42)
-- /blog/123             ==>  Just (Blog 123)
-- /blog/mosaic          ==>  Nothing
-- /user/tom/            ==>  Just (User "tom")
-- /user/sue/            ==>  Just (User "sue")
-- /user/bob/comment/42  ==>  Just (Comment "bob" 42)
-- /user/sam/comment/35  ==>  Just (Comment "sam" 35)
-- /user/sam/comment/    ==>  Nothing
-- /user/                ==>  Nothing
このURL.Parserパーサモジュールを使えば、URLをElmの素敵なデータへと完全に変換するコードを、とても簡潔に書くことができます!
例 2
個人用のブログで、次のようなURLのページがあるとしましょう。
- /blog/12/the-history-of-chairs
- /blog/13/the-endless-september
- /blog/14/whale-facts
- /blog/
- /blog?q=whales
- /blog?q=seiza
ここで、それぞれのブログ投稿のページがあり、またオプショナルなクエリパラメータとしてブログの概要を渡すこともできるものとします。このとき、URLパーサを書くために、Url.Parser.Queryモジュールを追加しておく必要があります。
import Url.Parser exposing (Parser, (</>), (<?>), int, map, oneOf, s, string)
import Url.Parser.Query as Query
type Route
  = BlogPost Int String
  | BlogQuery (Maybe String)
routeParser : Parser (Route -> a) a
routeParser =
  oneOf
    [ map BlogPost  (s "blog" </> int </> string)
    , map BlogQuery (s "blog" <?> Query.string "q")
    ]
-- /blog/14/whale-facts  ==>  Just (BlogPost 14 "whale-facts")
-- /blog/14              ==>  Nothing
-- /blog/whale-facts     ==>  Nothing
-- /blog/                ==>  Just (BlogQuery Nothing)
-- /blog                 ==>  Just (BlogQuery Nothing)
-- /blog?q=chabudai      ==>  Just (BlogQuery (Just "chabudai"))
-- /blog/?q=whales       ==>  Just (BlogQuery (Just "whales"))
-- /blog/?query=whales   ==>  Just (BlogQuery Nothing)
</>演算子や<?>演算子を使うと、パースしようとしている実際のURLによく似た形で、パーサを書くことができます。Url.Parser.Queryを追加すると、?q=seizaというようなクエリパラメータを扱うことができるようになります。
例 3
今度は、次のようなURLのページを持つ、ドキュメンテーションのウェブサイトを考えてみましょう。
- /Basics
- /Maybe
- /List
- /List#map
- /List#filter
- /List#foldl
Url.Parserモジュールにあるfragmentパーサを使うと、これらのアドレスのパーサを次のように書くことができます。
type alias Docs =
  (String, Maybe String)
docsParser : Parser (Docs -> a) a
docsParser =
  map Tuple.pair (string </> fragment identity)
-- /Basics     ==>  Just ("Basics", Nothing)
-- /Maybe      ==>  Just ("Maybe", Nothing)
-- /List       ==>  Just ("List", Nothing)
-- /List#map   ==>  Just ("List", Just "map")
-- /List#      ==>  Just ("List", Just "")
-- /List/map   ==>  Nothing
-- /           ==>  Nothing
同じように、URLフラグメントも扱うことができているのがわかります。
統合
ここまでいくつかのパーサを見てきましたが、Browser.applicationプログラムにこれをどのように組み入れればいいのかを見ていく必要があります。先ほどのように現在のURLを単に保持するのではなく、URLを役に立つデータへとパースして、それを表示することができるでしょうか?
TODO
ここで重要な新しい要素は次の2点です。
- UrlChangedメッセージを受け取ったときに、- updateはそのURLをパースします。
- view関数は異なるアドレスそれぞれについて異なる内容を表示します!
風変わりすぎるということはまったくありませんね。素晴らしいです!
しかし、10や20、あるいは100も異なるページがあるときはどうなるのでしょうか。ひとつのview関数にすべてを詰め込むのでしょうか?確かに、ひとつのファイルにすべてを書くというのは不可能です。いくつのファイルにわければいいのでしょうか? デイレクトリ構造はどうすべきでしょうか? これについては次の章で議論します!