[web-devel] [Yesod] widgets in default layout

Dmitry Kurochkin dmitry.kurochkin at gmail.com
Sun Feb 13 11:51:46 CET 2011


I am trying to make a menu widget for a site. It would render list of
menu items and mark the active one. I started with a new Menu module
that exports (mainMenu :: Widget ()) function. The function gets the
current route, iterates over a list of (item title, item route) list and
constructs the menu. Now I can import Menu in a handler module and use
^{mainMenu} in hamlet template.

Next I tried to put the menu widget to default layout - menu should be
on every page so default layout is where it belongs. Rest of the email
describes problems I got.

1. Module import loop.

I need to import the widget module in the module which defines
defaultLayout, i.e. the main application module. But the widget module
uses the main application module to have routes and probably other
staff, hence import loop. I tried to separate foundation and route
declaration from Yesod instance declaration, so that the widget module
could import just routes declaration and Yesod instance could import the
widget module. Turns out that does not work:

Attempting to interpret your app...
Compile failed:

    Couldn't match expected type `Route m'
           against inferred type `TestAppRoute'
      NB: `Route' is a type function, and may not be injective
    In the first argument of `\ u[a7Kv]
                                  -> hamlet-
                                       u[a7Kv] []', namely
    In a stmt of a 'do' expression:
        \ u[a7Kv]
            -> hamlet- u[a7Kv] []
    In the first argument of `hamlet-', namely
        `do { (hamlet-
             . preEscapedString)
                "<div id=\"menu\"><div class=\"left\"><div class=\"right\"></div><div class=\"container\"><a href=\"";
              \ u[a7Kv]
                  -> hamlet- u[a7Kv] []
             . preEscapedString)
                "\">itemTitle item</a></div></div></div>" }'

I do not understand details, but it is clear that the widget needs the
Yesod instance. So I have to put the widget in the main application
module. This does not look good considering that application may have
many widgets.

I have also stumbled upon a bug in devel server: It tries to recompile
the source in a loop, without changing source of course. Annoying but
not a critical issue.

2. Widget does not work in default layout.

I guess this is a known and expected behavior. My feeling is that
hamletToRepHtml can not embed widgets because it may be too late to add
cassius and julius. As a workaround I split default layout into outer
and inner layout. Outer layout renders just HTML <head> and <body>.
While outer layout is rendered as a widget that embeds the actual page
contents. Since outer layout is rendered as a widget, it may embed other
widgets like menu.

I imagine that hamletToRepHtml could render all embedded widgets before
the main body. Though, it may be difficult to implement, have
performance or other issues. Anyway, I think it is not uncommon to
include a widget in default layout. So Yesod should provide an easy way
to do it.

The last issue is that (mainMenu :: Widget ()) does not work, I had to
change it to (GWidget sub TestApp ()). Again I do not know details, but
this was unexpected to me. Perhaps the Widget type synonim should be

I would appreciate advices on how to solve the above problems. Perhaps I
am just missing something and there is a proper way to do what I want.
For now I will avoid using widgets in default layout and move part of
layout to individual handler templates, primarily because I find it too
ugly to define widgets in the main application module.


More information about the web-devel mailing list