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

Dmitry Kurochkin dmitry.kurochkin at gmail.com
Sun Feb 20 22:04:07 CET 2011


Hi Michael.

On Sun, 13 Feb 2011 19:13:06 +0300, Dmitry Kurochkin <dmitry.kurochkin at gmail.com> wrote:
> On Sun, 13 Feb 2011 17:44:33 +0200, Michael Snoyman <michael at snoyman.com> wrote:
> > On Sun, Feb 13, 2011 at 5:28 PM, Dmitry Kurochkin
> > <dmitry.kurochkin at gmail.com> wrote:
> > 
> > > Still the argument remains. I want to have all menu-related code in a
> > > separate module.
> > 
> > OK. As I said, this is possible, and it sounds like you already got
> > 90% of the way there. I don't know what's holding you up until I see
> > your code. My initial reaction is still that this is a bad idea in
> > general, though I *am* reconsidering that position. It might make
> > sense to alter the approach used by the scaffolded site.
> > 
> 
> I will try to send you some an example later. Unfortunately, I do not
> have time for this right now. Playing with Yesod in my free time only.
> 

I took another look at it. Turns out my problem was caused by using
getCurrentRoute in the widget. If there is no getCurrentRoute, it works
fine and I can use the widget in the defaultLayout.

Here is a simple code to demonstrate the issue:

    testWidget :: GWidget sub Test ()
    testWidget = do
        Just route <- lift getCurrentRoute
        addWidget [$hamlet|@{route}|]

Add the above code to the main application module. In the above case
Test is the foundation type. I get the following error:

    Couldn't match expected type `TestRoute'
           against inferred type `Route sub'
      NB: `Route' is a type function, and may not be injective

I would appreciate if you explain what the problem is and how to solve
it.

Regards,
  Dmitry

> > >> [snip]
> > >>
> > >> >> > 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.
> > >> >>
> > >> >> You should try looking at the scaffolded site: the function you want
> > >> >> to use is widgetToPageContent[1]. It converts a complete Widget into
> > >> >> the individual pieces that you need.
> > >> >>
> > >> >
> > >> > Yes, widgetToPageContent is used to convert the widget from handler and
> > >> > produces a set of pieces for page generation (pc). If I use it for a
> > >> > menu widget, I will get another PageContent (pc1). Now I need to take
> > >> > body from pc1, and merge other pieces of pc1 with pc. E.g. menu widget
> > >> > can produce javascript and CSS which needs to be merged with the main
> > >> > PageContent. I did not find an existing function to do this. Did I miss
> > >> > it?
> > >>
> > >> Just combine the two widgets and call widgetToPageContent once:
> > >>
> > >>     defaultLayout widget = do
> > >>         pc <- widgetToPageContents $ do
> > >>             menuWidget
> > >>             widget
> > >>         hamletToRepHtml ...
> > >>
> > >> You can see an example of this in the Yesod docs site[1].
> > >>
> > >
> > > I thought this would result in menuWidget placed directly before the
> > > main widget body, right? In many cases simple concatenation is not
> > > enough.
> > 
> > Then just modify menuWidget to take a Widget as an argument:
> > 
> > menuWidget :: GWidget s m () -> GWidget s m ()
> > menuWidget w = do
> >     earlierStuff
> >     w
> >     laterStuff
> > 
> > This is the very reason why we have polymorphic hamlet, so you can even do:
> > 
> > menuWidget w = [$hamlet|
> > <p>Header
> > ^{w}
> > <p>Footer
> > |]
> > 
> > In fact, if you put that into a separate menu-widget.hamlet file, you
> > might get the results you were looking for originally all the
> > back-bending.
> > 
> 
> This does not feel right in my case. Essentially, it moves part of
> layout to widget. If I have many widgets to combine, e.g. mainMenu,
> sideMenu, anotherCoolWidget, it becomes more complex. I think
> introducing a separate layout (my original workaround) is a better
> solution here. I just need to come up with a better name for it :)
> 
>     defaultLayout content = do
>         mmsg <- getMessage
>         pc <- widgetToPageContent $ do
>             addCassius $(Settings.cassiusFile "default-layout")
>             addWidget $(Settings.cassiusFile "inner-layout")
>         hamletToRepHtml $(Settings.hamletFile "outer-layout")
> 
> > [snip]
> > 
> > >> Just to confirm: are you talking for general widgets, or just widgets
> > >> to be called from defaultLayout? As I mention above, the former can
> > >> easily be put in separate modules, the latter would require more work.
> > >>
> > >
> > > I am talking about defaultLayout widgets. In general, reasons for
> > > putting widgets into separate modules does not depend on whether they
> > > are used in defaultLayout. Though, I agree that the fact that only
> > > defaultLayout widgets must be put into the Yesod instance module
> > > somewhat improves the situation.
> > >
> > > IMO from user point of view it does not matter much if widget is used in
> > > handler or in defaultLayout. In most cases, at least. I just create a
> > > menu widget and want be able to use it anywhere (even both in handlers
> > > and defaultLayout). E.g. in my case the only difference between handler
> > > and defaulLayout widget is the type signature, implementation does not
> > > change.
> > 
> > Nonetheless, these *are* the rules that exist in Haskell. To a certain
> > extent, separating out mkYesodDispatch into Controller.hs is a similar
> > hack to this. In that case, I think that the added benefit of separate
> > handler modules definitely justifies using this technique. I'm simply
> > not (yet) convinced that the benefit here is big enough to warrant a
> > similar approach.
> > 
> 
> Well, you have my vote for what it's worth :)
> 
> Regards,
>   Dmitry
> 
> > Michael



More information about the web-devel mailing list