Search code examples
urlhaskellyesodquery-string

Render url with query parameters


Can't find solution to a simple problem, the answer should be obvious. How to render url with a query parameters inside hamlet template?

I.e. @{ItemsR} will generate http://localhost:3000/items and how do I generate something like http://localhost:3000/items?page=10&sort=name?


Solution

  • Yesod is RESTful, you should use arguments in url format (eg. /items/page/10/sortby/name) if you wish use QueryString format then, you loss the Yesod type safe url management.

    Below example show how use different RESTful representation and a redirection (rendering a querystring url) to a request reading arguments from querystring

    {-# LANGUAGE QuasiQuotes #-}
    {-# LANGUAGE TypeFamilies #-}
    {-# LANGUAGE TemplateHaskell #-}
    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    import Yesod
    import Data.Text
    import Network.Wai.Handler.Warp (run)
    
    data RouteTest = RouteTest
    
    mkYesod "RouteTest" [parseRoutes|
    /item/all/page/#Int/sortby/#Text Items1R GET
    /items/page/#Int/sortby/#Text Items2R GET
    /items/#Int/#Text Items3R GET
    /itemsquery ItemsQueryR GET
    |]
    
    instance Yesod RouteTest where
        defaultLayout widget = do
            pc <- widgetToPageContent widget
            hamletToRepHtml [hamlet|\<!DOCTYPE html><html lang="en"><body>^{pageBody pc}</body></html>|]
    
    getItemsR :: Text -> Int -> Text -> HandlerT RouteTest IO Html
    getItemsR path page sortby =
      defaultLayout [whamlet|
        <h1>Items Page ##{page} sorted by #{sortby}
        <h4>(Using #{path} path format)
        <p>Same request in query string format
          <a href="@{ItemsQueryR}?page=#{page}&sort=#{sortby}">
            here
      |]
    
    getItems1R = getItemsR $ pack "large"
    getItems2R = getItemsR $ pack "medium"
    getItems3R = getItemsR $ pack "short"
    
    getItemsQueryR = do
      req <- getRequest
      defaultLayout [whamlet|
        <h1>Query String arguments format
        <ul>
          $forall (k, v) <- reqGetParams req
            <li>
              <b>#{k}</b>: #{v}
      |]
    
    main = run 8181 =<< toWaiApp RouteTest
    

    of course, you can write a helper to take some like your @{ItemsR page sortby}

    renderItemsQueryR page sortby = hamletToRepHtml [hamlet|@{ItemsQueryR}?page=#{page}&sort=#{sortby}|]
    

    but you must to use as

    ...
    url <- renderItemsQueryR page sortby
    defaultLayout [whamlet|
        ...
        <a href=#{url}>
        ...
    |]
    

    finally, you can write other helper to render any querystring url

    renderQueryString path keyValues = do
      let querystring = intercalate "&" $ map (\(k, v) -> concat [k, "=", v]) keyValues
      hamletToRepHtml [hamlet|@{path}?#{querystring}|]
    

    then, the renderItemsQueryR could be written as

    renderItemsQueryR page sortby = renderQueryString ItemsQueryR [("page"  , show page)
                                                                  ,("sortby", sortby   )]