How to use $maybe in hamlet - haskell

In Yesod, I've got a form that populates the type
data Field = Field Text Text text
deriving Show
When I write the hamlet html to display it, I'm running into the problem that Field is wrapped up in Maybe Maybe Field. So in hamlet I'm trying to do the following as shown here
(Snippet in postHomeR function)
let fieldData = case result of
FormSuccess res -> Just res
_ -> Nothing
(In the hamlet file)
<ul>
$maybe (Field one two three) <- fieldData
<li>#{show one}
However, when compiling there is Not in scope: one error. Why is the variable one not created/populated correctly?

You need to indent the <li> so that it's inside the $maybe block. As it sits now, it's a sibling to the $maybe, and thus the variables bound by $maybe are not in scope.

Related

Yesod Compare getCurrentRoute to a route with dynamic parameters

A question of coding haskell with good form, instead of 'it works'.
We want our menu navigation items to have a highlighted CSS class when the user is on that page.
Easy.
Here are my routes:
...
/simple SimpleR GET POST
/search SearchFormR GET POST
/searchresults/#Int/#Text SearchResultsR GET
...
In my hamlet template I have the below. This works perfectly, the link is highlighted when the user is on that page. The route is simple.
<li .nav-item>
<a .nav-link :Just SimpleR == mcurrentRoute:.active href=#{SimpleR}>
Simple page
This link should be highlighted when the user is on either of SearchFormR or SearchResultsR.
<li .nav-item>
<a .nav-link :((Just (SearchResultsR _ _)) == mcurrentRoute) || (Just SearchFormR == mcurrentRoute):.active href=#{SearchFormR}>
Search
This code does not work, because the SearchResultsR route constructor needs the two things passed to it. I cannot write _ _ as I have above, since the constructor needs a literal value. And besides, the result of the constructor will be compared with == to the actual route anyway, it won't match unless both parameters happen to be the exact same.
Without the _ _ I of course get a type error, expecting two more things passed to the SearchResultsR constructor.
That is, I want to check if we are on any SearchResultsR route, not only match SearchResultsR 123 "abc".
My first thought was to make a function to convert the route to a string and then check if that string contains the string of the route, but this is bad, and not the haskell / typesafe way.
My second attempt is using pattern matching:
Foundation.hs:
isThisRouteSearchResultsR :: Maybe (Route App) -> Bool
isThisRouteSearchResultsR (Just (SearchResultsR _ _)) = True
isThisRouteSearchResultsR _ = False
Hamlet template:
<li .nav-item>
<a .nav-link :(isThisRouteSearchResultsR mcurrentRoute) || (Just SearchR == mcurrentRoute) :.active href=#{SearchR}>
Search
But this doesn't seem much better. What if I have a lot more routes that have dynamic parts?
What is the proper way to do this? I looked at haskellers site source code but couldn't find the answer
The answer given by Sibi in the comments (thank you Sibi) is that the second approach in the question is in good form / style.
e.g. in hamlet:
:(isThisRouteSearchResultsROrSearchR mcurrentRoute) :.active
Or could do
:(isThisRouteSearchResultsR mcurrentRoute) || (isThisRouteSearchR mcurrentRoute) :.active
And you define these functions in Foundation.hs (Haskell) as per the question code.
Sibi:
The goal is to usually keep all your logic in the Haskell file and
keep it minimal in the Hamlet file.
Thank you Sibi for your guidance.

run whamlet in pure code?

In a yesod application, I want to create URL attributes for a graph that will be rendered by graphviz , and I want to use interpolation. Ideally,
graphToDot nonClusteredParams { fmtNode = \ (n,l) ->
[ URL [whamlet| #{MyRoute ...} |]
} g
Of course, the types don't match:
attribute of URL is pure Text, but whamlet is monadic (widget)
when I replace by shamlet, type is fine, but it cannot interpolate: URL interpolation used, but no URL renderer provided
Is there an easy way to solve this?
This works: get the render function (in the monad), and apply (in pure code)
render <- getUrlRender
let d = graphToDot ...
[ URL $ render $ MyRoute ... ]
I found this here, where a similar problem is solved: https://github.com/yesodweb/yesod/wiki/Using-type-safe-urls-from-inside-javascript

Xml-conduit parsing for text inside links

I would like to extract the text contents from the below Html page. All the paragraphs from the <div>.
I use the xml-conduit package for html parsing and came up with the following code:
getWebPageContents :: Url -> IO [T.Text]
getWebPageContents u = do
cursor <- cursorFor u
return $ cursor $// filter &/ content
filter = element "div" >=> attributeIs "id" "article-body-blocks" &// element "p"
This will return most of the text but not the ones from the links("front page of today's Daily Mirror")
Could anyone help?
You need to filter to all the descendants of the p tags, not just the children. You probably just need to replace &/ content with &// content.

Writing Yesod test case for handler with an id parameter, where id is a key to an Entity

I have been following the yesod tutorial and I am stuck on how to build a unit test involving parameters in a view that also hit a database. Backtracking a little, I followed the Echo.hs example:
getEchoR :: Text -> Handler Html
getEchoR theText = do
defaultLayout $ do
$(widgetFile "echo")
The corresponding test, note I have to cast the parameter into Text using Data.Text.pack
yit "Echo some text" $ do
get $ EchoR $ pack "Hello"
statusIs 200
Now I have the model defined like so:
Tag
name Text
type Text
With a handler that can render that that obviously take a TagId as the parameter
getTagR :: TagId -> Handler Html
getTagR tagId = do
tag <- runDB $ get404 tagId
defaultLayout $ do
setTitle $ toHtml $ tagName tag
$(widgetFile "tag")
This is where the test fails.
yit "Get a tag" $ do
-- tagId is undefined
get $ TagR tagId
statusIs 200
I am not sure how to define the tagId. It wouldn't work with a String or Text or Num, and I can't seem to figure out how to generate one as I can't find any example code in various Data.Persist tutorials. Or better yet, some other way to call the get method.
You want to use the Key data constructor to construct an ID value, which takes a PersistValue as a parameter. A simple example of creating one is:
Key $ PersistInt64 5
Another option is to call get with a textual URL, e.g. get ("/tag/5" :: Text).
Since times have changed, I'll leave this note here to say that these days one would use something like:
fromBackendKey 5
See the docs for fromBackendKey.

Using UTCTime with Hamlet

I am using Yesod on my first site and I have a list of news items:
NewsItem
date UTCTime default=CURRENT_TIME
title String
content String
author String
which are retrieved in my handler:
newsitems <- runDB $ selectList [] [Desc NewsItemDate]
and ultimately used in my template:
$if null newsitems
<p>No news.
$else
$forall Entity id entry <- newsitems
<article>
<h4>#{newsItemDate entry}
<p>#{newsItemContent entry}
But I get an error about datatypes:
Handler/Home.hs:20:11:
No instance for (Text.Blaze.ToMarkup
time-1.4:Data.Time.Clock.UTC.UTCTime)
arising from a use of `toHtml'
Possible fix:
add an instance declaration for
(Text.Blaze.ToMarkup time-1.4:Data.Time.Clock.UTC.UTCTime)
In the first argument of `toWidget', namely
`toHtml (newsItemDate entry_a6ev)'
In a stmt of a 'do' block:
toWidget (toHtml (newsItemDate entry_a6ev))
In the expression:
do { toWidget
((Text.Blaze.Internal.preEscapedText . Data.Text.pack)
"<article><h4>");
toWidget (toHtml (newsItemDate entry_a6ev));
toWidget
((Text.Blaze.Internal.preEscapedText . Data.Text.pack)
"</h4>\
\<p>");
toWidget (toHtml (newsItemContent entry_a6ev));
.... }
So I figure I would go ahead and add to my Import.hs:
import Data.Time (UTCTime)
import Data.Time.Format (formatTime)
import Text.Blaze (ToMarkup, toMarkup)
import Text.Blaze.Internal (string)
import System.Locale (defaultTimeLocale)
-- format date as 26 July 2012
instance ToMarkup UTCTime where
toMarkup a = string (formatTime defaultTimeLocale "%e %B %Y" a)
Which does compile, but gives me an error at runtime in the browser:
Internal Server Error
PersistMarshalError "Expected UTCTime, received PersistText \"2012-08-30\""
So I am not sure how to solve this, any ideas?
EDIT: Source code to the site in case it is needed or curious: https://github.com/iaefai/socrsite
Without investigating the actual error, I think your approach is not great. You will very likely eventually want several ways of formatting a UTCTime, after all, the type is there to store times, not just dates. By giving a ToMarkup UTCTime instance, you fix this globally.
I would recommend to write functions renderAsDate :: UTCDate -> HTML, renderAsTime :: UTCDate -> HTML etc. and use them in your template, e.g. #{renderAsDate (newsItemDate entry)}.
But this won’t solve the runtime error, which comes from the serialization layer and is likely independent of your templates.
I'm pretty sure you could just use show in the hamlet? That's atleast what I've done...
#{show $ newsItemDate entry}
I've run into this instance thing before, and as this guy describes here it's something like this:
As part of this philosophy of frugality of expression Haskell doesn’t
require type signatures — although an experienced Haskeller provides
them for clarity — so type errors in this strongly typed language are
often cryptic for the uninitiated. For instance, if you define a
function f that adds two numbers and then call it with two strings,
the compiler will not complain about bad arguments, it will complain
about string not supporting operator plus. And it will formulate this
complaint in a very non-obvious way.
[1] Under "1. Haskell is Terse"...
[1] http://fpcomplete.com/ten-things-you-should-know-about-haskell-syntax/

Resources