Placing Content in Multiple Zones

Feb 11, 2011 at 7:43 AM

Hi, I have been evaluating this product for a few days now and so far I have found it to be very clean and intuitive.

However there is something I am wanting to accomplish and I have not been successful with it yet.  I have some related text that goes with each page that needs to show up outside the main content area.  The text is associated with the page text so I'd like to be able to manage it together.

I've augmented the "Page" Content Type with a custom Content Part, and I'd like to get that to show up in a separate zone of the resulting page ("Footer"). First I tried using Placement.info in the content part module to try to output the part into "Footer" during Display but I read in a different thread that this is not possible because of the zone hierarchy.

So I'm thinking I have two choices:

1. Modify the theme so that there is more than one zone where I can output content; or 2. create a widget that I can place on each page in the Footer that will reference the current content item and display the value of that content part.

I'm sure either method is possible, but I can't figure out how to do either of these.  For 1. I don't know how to create more content zones and for 2. I don't know how to reference the current content item from a widget.

Can someone point me to some docs that would help me (or even show me how to do this)? or maybe I'm approaching this incorrectly?

Feb 11, 2011 at 10:00 AM

I had the same problems, and since it are all dynamic nested shapes it's nearly impossible to see what is inside.

If u can get it to work without using 'widgets' then i'll be intrested, but i think it's ment to render a shape within the zone of the parent shape.
If the parent shape is rendered within the content zone then u need to split up the content zone into new zones.

Feb 11, 2011 at 5:44 PM

I suppose my post is a duplicate of this one:

http://orchard.codeplex.com/discussions/242320

So the answer may be that there is no out-of-box way to do this.

It's possible I'm just not understanding shapes and zones yet.

Coordinator
Feb 11, 2011 at 5:55 PM
Edited Feb 11, 2011 at 6:29 PM

No, what's in the other thread is a little different.

You can perfectly well access the layout (from the work context for example) and go to zones from here and add new shapes. Here's a piece of code I wrote yesterday that does that for example:

 

dynamic linkShape = _shapeFactory.Create("Vandelay_ThemePicker_LinkToDefault");
var zone = _workContext.Layout.Zones[selectedThemeRule.Zone];
zone.Add(linkShape, selectedThemeRule.Position);

Oh, and an interesting twist here is that at the moment when this code is running, that zone doesn't even exist yet :)

Feb 13, 2011 at 3:44 PM
Edited Feb 13, 2011 at 4:51 PM

Bertrand:

Thank you for your reply.

However, I'm sorry but I don't think I understand the product well enough yet to understand your response.

What I want is to be able to display parts of my content to different parts of the resulting output page...

Feb 13, 2011 at 11:18 PM

This is similar to the problem I had with displaying the title/body of a container or list along with the list of contained items. I was able to add the title to the SubPageTitle layout area. http://orchard.codeplex.com/discussions/242150

Basically, what I found was I needed to create a new shape and add it to the dynamically created ViewModel which means you can name the new shape whatever you want then use it within the view that is returned, in my case from Display.cshtml in Orchard.Core\Containers\View\Item. So I passed my new shape to the Model and added a new @Display(Model.Container) to Display.cshtml, which then renders my shape which I also called Container, so then in my theme I added a View\Container.cshtml, which I can use to display the Text I passed to my new shape and the Title which I wanted to show up in the SubPageTitle layout section, not the normal content zone, so from looking at Layout.cshml in my theme I could see they called things like this: WorkContext.Layout.Footer.Add(New.BadgeOfHonor(), "5"); I'm not sure what the 5 is for, I'm guessing the position, but made me think maybe I could add my new shape to one of these areas which gets rendered in the different zones. In my theme theres a SubPageTitle layout section that I could see was only referenced from Layout.cshtml and Content.cshtml. In Layout it was using Zone which is an alias for Display to render the shapes in it, @Zone(Model.SubPageTitle), and in Content.cshtml I could see they were adding a shape to SubPageTitle just like the BadgeOfHonor adding to the Footer area. So in my theme's View\Container.cshtml (which I just created for my new shape) I added @WorkContext.Layout.SubPageTitle.Add(Model.Title, "12"). And Model.Title is one of the dynamic parameters I used when creating my new shape.

This to me a the beauty of Orchard. Its very confusing at first because its all dynamic objects and you can't use intellisense to even find what you're looking for, but once you find where your shapes are being created and the params used to create them, then you start to see how amazing it all is. Each Theme and each type should maybe have a cheat sheet with it that tells you what shapes it uses and the parameters available to you in your view through Model.

Once I realized I can add my own shapes to each zone/area no matter where I am in the request building it made much more sense. I was thinking the building of the page was more linear, for example when I was in the view for Content, I could see from the Layout.cshtml that the html I generated here would be placed inside the html tags at this location, which is true, but that doesn't mean that the other zones are already built and rendered. They are not set in stone and you can add shapes to any of them from any other place. This is amazing to me because now it opens a whole new world where any module can modify what is displayed in any other area no matter where in the request flow your module is.

I hope that helps. This is what started to make everything click for me. Its still difficult to find the Controllers for some shapes, which is where it would be nice to have a cheat sheet for these things.

Feb 14, 2011 at 11:03 AM

Thank you, themicster.  I think I understand what you are doing.

Though, I would feel much better about this if there was a way to do this without modifying Core.

Also, calling Shape.Add from a cshtml file feels like a hack; I don't think that's doing it in the correct layer.

I'll experiment about this a little bit more, and post here again if I come up with something.

Thank you again.

Coordinator
Feb 14, 2011 at 7:53 PM

@harmony7: you do not need to modify core in anything that was described. Everything that has been said can be done from a theme (overriding content.cshtml, adding a Orchard.Core\Containers\View\Item.cshtml to the Views folder of your theme, etc.) or from a module.

Calling Shape.Add from a cshtml file is perfectly fine: while rendering, you are simply dispatching part of the rendering to a different zone. If you can do it from a driver for example that is always cleaner but you shouldn't feel bad about moving shapes around from templates.

Feb 14, 2011 at 9:38 PM

@bertrandleroy, what about the Container\Controllers\ItemController.cs? How would you override that in a theme?

Coordinator
Feb 14, 2011 at 10:59 PM
Edited Feb 14, 2011 at 10:59 PM

What's the scenario for that? Why can't you define your own controller?

Feb 15, 2011 at 5:08 AM

Thats exactly the question, how does one go about creating their own controller for a part in core? Do we hook into events for this? And we need access to the same data as the controller in the core.

Coordinator
Feb 15, 2011 at 5:11 AM

Why do you absolutely want that controller to be defined in core? There is nothing magical about core, you can define controllers from any module, with your own route and from there you'll be able to access exactly the same data as the controllers in core.

Feb 15, 2011 at 5:14 AM

Which one will be used then? How do I make sure the core controller and my controller are both used? And in what order do they get executed?

Feb 15, 2011 at 5:47 AM

I'm testing this and its not working. Can I not have Routes.cs in a theme?

Coordinator
Feb 15, 2011 at 5:50 AM

Sure but you need a csproj for the theme and you need that file to be referenced by the csproj. Having your route take precedence is just a matter of setting a higher priority in the route descriptor.

Feb 15, 2011 at 6:23 AM

Ok, I'm trying this, and I've moved it to a module anyways, but I had it in the contoso theme project so it should have been fine. Anwyays, now its in a simple module I created, and I hang while trying to debug it, but it runs without the debugger fine, but doesn't display my new shape. I'm not sure which shape is being displayed from ViewModel so I just figured it was the same as in core and copied Display.cshtml from View\Item\ to my project in the same location. Something is still wrong, but I can't debug so I don't know if the routes are even getting created. More tinkering and I should have this figured out. Also I saw what you and digital11 were talking about in IRC. I think the shape table provider hook sounds like it might be better for my case too, but I don't quite get that but I have a few places I'll start looking because I think I remember seeing that in some other modules or core.

Coordinator
Feb 15, 2011 at 7:09 AM

It's quite likely, yes, there is rarely a need to write a controller, but that's what you asked for ;) Let me know how that goes.

Feb 15, 2011 at 8:48 AM

Having no luck with this. I did get it to hit the breakpoint in my new controller without the constraints, but with the IContainersPathConstraint it doesn't seem to get called. Also the constraint is empty when my Routes.cs is called. I think its time to throw in the towel on this one and go a different route or just put it back in core for now till I have more time.

Feb 15, 2011 at 2:08 PM

All right, thank you guys!  After reading the above discussion numerous times and several hours of experimentation, I was able to get what I wanted.

What I ended up doing was this:

1. Added a Zone field to the content part record (in addition to the Text field), so that I could associate the related text with the zone I want to display it in.  The Display method in the driver creates a shape with two fields: Text and Zone.

2. The corresponding cshtml file in Views/Parts/ renders nothing.  Instead, it does this:

@{
    if(Model.Metadata.DisplayType == "Detail") {
        var zone = WorkContext.Layout.Zones[Model.Zone];
        zone.Add(New.RelatedTextBody(Html: new HtmlString(Model.Text)));
    }
}

3. Created a new cshtml file in Views/ called RelatedTextBody.cshtml, which contains just one line:

@Model.Html

That's it.

I have a question however.  Can someone tell me why this works?

What I mean is, it feels to me that this method would require my content part to be rendered before the zone I'm adding the new shape into.  However, this works even if I target a zone that comes earlier than Content (in the theme's Layout.cshtml).

Coordinator
Feb 15, 2011 at 7:06 PM

Magic. No seriously when you access a zone that doesn't exist, we just create an empty shape, which is the same thing. Other pieces of code trying to do stuff to that shape will either create it or just act on the existing one. That makes the order in which stuff happens pretty much irrelevant.

Feb 15, 2011 at 7:45 PM

@harmony7, Yes, thats kind of that I was eluding to in a previous post as well. I didn't realize how awesome this was until I realized that execution order didn't even matter. Which is liberating because it frees you from trying to think linear through the code and all the modules, so you are free to just focus on what your doing. Simply beautiful.