
Hi Michael.
On Sun, 13 Feb 2011 19:13:06 +0300, Dmitry Kurochkin
On Sun, 13 Feb 2011 17:44:33 +0200, Michael Snoyman
wrote: On Sun, Feb 13, 2011 at 5:28 PM, Dmitry Kurochkin
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