haskellyesodhamlet

How can I embed both Text and Widget in another Widget in Yesod?


Given a simple example like this

glyphicon :: Text -> Widget
glyphicon name = toWidget [hamlet|<span class="glyphicon glyphicon-#{name}">|]

foo :: ToMarkup a => a -> Widget
foo content = toWidget [hamlet|<div class="foo">#{content}</div>|]

Is there a builtin mechanism in Yesod which would allow me to do both foo "some text" and foo (glyphicon "pencil")? I've managed to work around this by using a custom typeclass which converts both Text and Widget to Widget

class MakeWidget a where
  makeWidget :: a -> Widget

instance MakeWidget Widget where
  makeWidget = id

instance MakeWidget Text where
  makeWidget x = toWidget [hamlet|#{x}|]

glyphicon :: Text -> Widget
glyphicon name = toWidget [hamlet|<span class="glyphicon glyphicon-#{name}">|]

foo :: MakeWidget a => a -> Widget
foo content = toWidget [whamlet|<div class="foo">^{makeWidget content}</div>|]

but this doesn't feel right, especially since I can't even do ^{foo "hello"} due to ambiguous types, and have to do ^{foo (T.pack "hello")} instead.

Is there a better way of embedding both Text and Widget inside another Widget?


Solution

  • I wouldn't go this route, specifically because of the type inference issue you already discovered. Explicitly having to call toWidget occasionally is probably the best compromise here.