Displaying the same content twice on one page

Topics: General, Writing themes
Editor
Jan 23, 2012 at 2:27 PM

I have a content type that is rendering in summary view on my page. This is all good. I can override the template for it with Content-ContentType.Summary.cshtml. If I want to create a section above the main content area with JumpTo links for each item below, I am having trouble doing this. I added a widget in the BeforeContent zone that is a Container for the list of content items. Can I name templates based on zonename? I have not seen this successfuly done yet. I would like something like this : Content-BeforeContent-ContentType.Summary.cshtml.

I know I can get some other alternates based on using shape tracing, but are zonenames allowed too?

Thanks

Jan 23, 2012 at 4:48 PM

I have the same Problem. I have two Layers(Content & AsideSecond). On the Content I want to show the DetailView and on the AsideSecond I want to show the Summary View.

I tried to do this over the placement.info, but it doesn't work.

I just want to use 2 different .cshtml files behind.

 

May somebody could help?

Jan 23, 2012 at 5:25 PM

It's easiest to use a different Display Type if you're rendering the content with a call to IContentManager.BuildDisplay. You can make up any new display type you like, provide a custom Content.{DisplayType}.cshtml templates, and define Placement for all its parts from scratch. You can use <Place Parts_SomePart="Content:5;alternate=A_Different_Tempate"/> where you want to make parts look different on your new display type.

Schoky's problem is slightly different, how to actually get something into a different Layout Zone. The important thing here is that Content zones (which you can control via placement) are just the zones within the Content.cshtml shape. The zones in Layout.cshtml are completely separate and you can't (yet...) target them with placement. Well there are a loads of ways around this. You can build a Widget, or you can do it from a ContentPartDriver and just insert a shape into WorkContext.Layout, you can even use my Mechanics module which has a feature called Paperclips; Mechanics allows you to define a relationship between content items, and Paperclip lets you configure it so the related content is pushed off to a different zone.

However: if you want a proper solution for using Placement.info to target the Layout Zones, I've come up with a little something, it'll be in Part 2 of my Clay tutorial which I'll hopefully put up today :)

Editor
Jan 23, 2012 at 5:36 PM
So you have managed to confuse me a little more. I can just do the following for a Content Type called "Employee"

1. Add this line entry to placement file: <Place Parts_Employee="Content:5;alternate=JumpToLinks"/>
2. Then add a cshtml file to my theme called Content-Employee.JumpToLinks.cshtml

Then on my listing page for Employees, I can define a widget above the actual listing that renders using the JumpToLinks.cshtml template?

Arra

On Mon, Jan 23, 2012 at 12:25 PM, randompete <notifications@codeplex.com> wrote:

From: randompete

It's easiest to use a different Display Type if you're rendering the content with a call to IContentManager.BuildDisplay. You can make up any new display type you like, provide a custom Content.{DisplayType}.cshtml templates, and define Placement for all its parts from scratch. You can use <Place Parts_SomePart="Content:5;alternate=A_Different_Tempate"/> where you want to make parts look different on your new display type.

Schoky's problem is slightly different, how to actually get something into a different Layout Zone. The important thing here is that Content zones (which you can control via placement) are just the zones within the Content.cshtml shape. The zones in Layout.cshtml are completely separate and you can't (yet...) target them with placement. Well there are a loads of ways around this. You can build a Widget, or you can do it from a ContentPartDriver and just insert a shape into WorkContext.Layout, you can even use my Mechanics module which has a feature called Paperclips; Mechanics allows you to define a relationship between content items, and Paperclip lets you configure it so the related content is pushed off to a different zone.

However: if you want a proper solution for using Placement.info to target the Layout Zones, I've come up with a little something, it'll be in Part 2 of my Clay tutorial which I'll hopefully put up today :)

Read the full discussion online.

To add a post to this discussion, reply to this email (orchard@discussions.codeplex.com)

To start a new discussion for this project, email orchard@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com




--
Thanks,

Arra

Jan 23, 2012 at 5:51 PM

@arock: No, that's not at all what I was saying. There are a few problems there:

a) There isn't a Parts_Employee. You said yourself, Employee is a Content Type, not a Content Part. The only things that Placement can affect are shapes emitted from a ContentPartDriver. And there's no "Content" shape emitted from a driver; it's what the drivers send shapes into.

b) If you have "alternate=JumpToLinks" on a valid shape, then your template would be JumpToLinks.cshtml

c) I have no idea what you're saying about widgets or what relationship it would have to the previous statements. Widgets are just a completely separate mechanism you can use to render stuff in the various Layout Zones. So you *can* create your own widget in which you can render your JumpToLinks (but this will be done using a custom part and a ContentPartDriver, not by overriding the Content.cshtml, or Widget.cshtml in the case of widgets)

Editor
Jan 23, 2012 at 5:56 PM
@randompete

So here is the scenario: So I have a content type of Employee which then is organized into a List. This list has a URL to the employee listing page. At the top of the page we want some general JumpTo links for the employees listed below. My thoughts on building this is having the Content-Employee.Summary.cshtml to render my employees correctly on the page. Secondly, to generate the JumpTo links I put a Container Widget in the BeforeContent zone which is list of the employees as well. My problem is finding an alternate template to control the rendering of this section. It would be great to just be able to target the Content Type by Zone here. I am a bit confused by your first response. If you could spell it out in steps I would really appreciate it.

Thanks

On Mon, Jan 23, 2012 at 12:51 PM, randompete <notifications@codeplex.com> wrote:

From: randompete

@arock: No, that's not at all what I was saying. There are a few problems there:

a) There isn't a Parts_Employee. You said yourself, Employee is a Content Type, not a Content Part. The only things that Placement can affect are shapes emitted from a ContentPartDriver. And there's no "Content" shape emitted from a driver; it's what the drivers send shapes into.

b) If you have "alternate=JumpToLinks" on a valid shape, then your template would be JumpToLinks.cshtml

c) I have no idea what you're saying about widgets or what relationship it would have to the previous statements. Widgets are just a completely separate mechanism you can use to render stuff in the various Layout Zones. So you *can* create your own widget in which you can render your JumpToLinks (but this will be done using a custom part and a ContentPartDriver, not by overriding the Content.cshtml, or Widget.cshtml in the case of widgets)

Read the full discussion online.

To add a post to this discussion, reply to this email (orchard@discussions.codeplex.com)

To start a new discussion for this project, email orchard@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com




--
Thanks,

Arra

Jan 23, 2012 at 6:10 PM
Edited Jan 23, 2012 at 6:12 PM

ContainerPart renders a list of content items. It renders these in "Summary" display type. Due to various details of the way that Zones and containers work, even if ContainerPart knew what zone it was in, it's not passing that information further down into the list it renders. This is kind of a sticky situation, because you can only customise the way the list renders based on that "Summary" display type, which is the same as every other list; so any changes you make in placement will affect all other lists.

So you have to completely take over rendering and generate a list using a different display type. And remember it's not just the Content shape that you need to customise, it's certain part shapes as well (presumably Parts_RoutableTitle, because you'll need to customise the urls it generates to make jump links instead of content urls).

The easiest option to do this is probably writing your own ContentPartDriver<ContainerPart>. Basically copy the existing one (Orchard.Core/Containers/Drivers/ContainerPartDriver.cs - assuming you're on a recent Orchard that has that). The list is generated thusly:

 

        return ContentShape("Parts_Container_Contained",
                            () => {
                                var container = part.ContentItem;

                                IContentQuery<ContentItem> query = _contentManager
                                .Query(VersionOptions.Published)
                                .Join<CommonPartRecord>().Where(cr => cr.Container.Id == container.Id);

                                var descendingOrder = part.OrderByDirection == (int)OrderByDirection.Descending;
                                query = query.OrderBy(part.OrderByProperty, descendingOrder);
                                 var metadata = container.ContentManager.GetItemMetadata(container);
                                 if (metadata!=null)
                                    _feedManager.Register(metadata.DisplayText, "rss", new RouteValueDictionary { { "containerid", container.Id } });

                                var pager = new Pager(_siteService.GetSiteSettings(), part.PagerParameters);
                                pager.PageSize = part.PagerParameters.PageSize != null && part.Paginated
                                                ? pager.PageSize
                                                : part.PageSize;

                                var pagerShape = shapeHelper.Pager(pager).TotalItemCount(query.Count());

                                var startIndex = part.Paginated ? pager.GetStartIndex() : 0;
                                var pageOfItems = query.Slice(startIndex, pager.PageSize).ToList();

                                var listShape = shapeHelper.List();
                                listShape.AddRange(pageOfItems.Select(item => _contentManager.BuildDisplay(item, "Summary")));
                                listShape.Classes.Add("content-items");
                                listShape.Classes.Add("list-items");

                                return shapeHelper.Parts_Container_Contained(
                                    List: listShape,
                                    Pager: pagerShape
                                );
                            });

All you need to do is change "Summary" to "JumpList" and hey presto, you can now both customise the Content shape as Content.JumpList.cshtml, as well as target your new list in Placement.info with <Match DisplayType="JumpList"> ...

Jan 23, 2012 at 6:18 PM
Edited Jan 23, 2012 at 6:19 PM

By the way: since you're creating a custom driver, there's no need to use a widget to do all this. Instead of using "return ContentShape(...)" you can just create a shape and push it directly into Layout:

 

if (displayType=="Detail") {

  // ... render items and construct list here

  // Now push a shape into Layout:
  Services.WorkContext.Layout.BeforeContent.Add(
               shapeHelper.Parts_Container_Contained(
                                        List: listShape,
                                        Pager: pagerShape
                                    ),"1");
}

The major problem with doing that is it's not very reusable, you're pushing it to a specific zone and position, so you might have to write a customised version each time you had a slightly different layout. This is why I've (for quite some time) been trying to come with a solution to push shapes to Layout just using Placement.info. I finally found a decent method and will post it today.

Editor
Jan 23, 2012 at 6:20 PM

Two things:

1. Will this cause all other container widgets to generate display types of JumpTo list as well? Or just my new type? Seems like it would cause this to happen to all of them.

2. When I copy the Orchard.Core/Containers/Drivers/ContainerPartDriver.cs file, do I just place it in my theme in a drivers folder, or do I need to recompile the source after I do it?

 

Thanks

Jan 23, 2012 at 6:47 PM

1. Yes it would happen on all of them. Which is another reason why I want layout pushes in Placement.info, because then you could match content types and show or hide the additional shape at your discretion. So you can either do this from a widget to retain some flexibility; or just hardcode it with: if (part.ContentItem.ContentType=="MyType") { ... }

2. You can place it in any module/theme in any folder, but by convention and to make them easy to find drivers go in /Drivers. Just make sure you change the namespace to something custom so . If you don't have a .csproj (/CreateProject:true) in your theme there could be problems. Orchard will dynamically recompile your code as long as the .csproj is updated, you shouldn't have to build manually.

Editor
Jan 23, 2012 at 9:10 PM

I am in src\Orchard.Web\Core\Containers\Driver\ContainerPartDriver.cs and don't see the above code. Has it moved?

Jan 23, 2012 at 9:42 PM

Ok, this was a change in 1.x branch I think, previously containers were rendering their list in a specialised controller. You'll find approximately the same code in Containers\Controllers\ItemController - but there's no reason not to just copy and paste the new driver version for your alternate rendering.

Editor
Jan 23, 2012 at 10:04 PM

It looks like the ContainerPartDriver has way more logic in it now than just the list rendering. 

 public class ContainerPartDriver : ContentPartDriver {
        private readonly IContentDefinitionManager _contentDefinitionManager;

        public ContainerPartDriver(IContentDefinitionManager contentDefinitionManager, IOrchardServices orchardServices) {
            _contentDefinitionManager = contentDefinitionManager;
            Services = orchardServices;
            T = NullLocalizer.Instance;
        }

        public IOrchardServices Services { get; private set; }
        public Localizer T { get; set; }

        protected override DriverResult Display(ContainerPart part, string displayType, dynamic shapeHelper) {
            return Combined(
                ContentShape("Parts_Container_Contained",
                             () => shapeHelper.Parts_Container_Contained(ContentPart: part)),
                ContentShape("Parts_Container_Contained_Summary",
                             () => shapeHelper.Parts_Container_Contained_Summary(ContentPart: part)),
                ContentShape("Parts_Container_Contained_SummaryAdmin",
                             () => shapeHelper.Parts_Container_Contained_SummaryAdmin(ContentPart: part))
                );
        }

        protected override DriverResult Editor(ContainerPart part, dynamic shapeHelper) {
            // if there are no containable items then show a nice little warning
            if (!_contentDefinitionManager.ListTypeDefinitions()
                .Where(typeDefinition => typeDefinition.Parts.Any(partDefinition => partDefinition.PartDefinition.Name == "ContainablePart")).Any()) {
                Services.Notifier.Warning(T("There are no content types in the system with a Containable part attached. Consider adding a Containable part to some content type, existing or new, in order to relate items to this (Container enabled) item."));
            }

            return Editor(part, (IUpdateModel)null, shapeHelper);
        }

        protected override DriverResult Editor(ContainerPart part, IUpdateModel updater, dynamic shapeHelper) {
            return ContentShape(
                "Parts_Container_Edit",
                () => {
                    var model = new ContainerViewModel { Part = part };
                    // todo: is there a non-string comparison way to find ContainableParts?
                    var containables = _contentDefinitionManager.ListTypeDefinitions().Where(td => td.Parts.Any(p => p.PartDefinition.Name == "ContainablePart")).ToList();
                    var listItems = new[] { new SelectListItem { Text = T("(Any)").Text, Value = "" } }
                        .Concat(containables.Select(x => new SelectListItem {
                            Value = Convert.ToString(x.Name),
                            Text = x.DisplayName,
                            Selected = x.Name == model.Part.Record.ItemContentType,
                        }))
                        .ToList();

                    model.AvailableContainables = new SelectList(listItems, "Value", "Text", model.Part.Record.ItemContentType);

                    if (updater != null) {
                        updater.TryUpdateModel(model, "Container", null, null);
                    }

                    return shapeHelper.EditorTemplate(TemplateName: "Container", Model: model, Prefix: "Container");
                });
        }

        protected override void Importing(ContainerPart part, ImportContentContext context) {
            var itemContentType = context.Attribute(part.PartDefinition.Name, "ItemContentType");
            if (itemContentType != null) {
                if (_contentDefinitionManager.GetTypeDefinition(itemContentType) != null) {
                    part.Record.ItemContentType = itemContentType;
                }
            }

            var paginated = context.Attribute(part.PartDefinition.Name, "Paginated");
            if (paginated != null) {
                part.Record.Paginated = Convert.ToBoolean(paginated);
            }

            var pageSize = context.Attribute(part.PartDefinition.Name, "PageSize");
            if (pageSize != null) {
                part.Record.PageSize = Convert.ToInt32(pageSize);
            }

            var orderByProperty = context.Attribute(part.PartDefinition.Name, "OrderByProperty");
            if (orderByProperty != null) {
                part.Record.OrderByProperty = orderByProperty;
            }

            var orderByDirection = context.Attribute(part.PartDefinition.Name, "OrderByDirection");
            if (orderByDirection != null) {
                part.Record.OrderByDirection = Convert.ToInt32(orderByDirection);
            }
        }

        protected override void Exporting(ContainerPart part, ExportContentContext context) {
            context.Element(part.PartDefinition.Name).SetAttributeValue("ItemContentType", part.Record.ItemContentType);
            context.Element(part.PartDefinition.Name).SetAttributeValue("Paginated", part.Record.Paginated);
            context.Element(part.PartDefinition.Name).SetAttributeValue("PageSize", part.Record.PageSize);
            context.Element(part.PartDefinition.Name).SetAttributeValue("OrderByProperty", part.Record.OrderByProperty);
            context.Element(part.PartDefinition.Name).SetAttributeValue("OrderByDirection", part.Record.OrderByDirection);
        }
    }

    public class ContainerPartHandler : ContentHandler {
        public ContainerPartHandler(IRepository repository) {
            Filters.Add(StorageFilter.For(repository));
            OnInitializing((context, part) => {
                part.Record.PageSize = part.Settings.GetModel().PageSizeDefault
                                        ?? part.PartDefinition.Settings.GetModel().PageSizeDefault;
                part.Record.Paginated = part.Settings.GetModel().PaginatedDefault
                                        ?? part.PartDefinition.Settings.GetModel().PaginatedDefault;

                // hard-coded defaults for ordering
                part.Record.OrderByProperty = part.Is() ? "CommonPart.CreatedUtc" : string.Empty;
                part.Record.OrderByDirection = (int)OrderByDirection.Descending;
            });
        }
    }

Jan 23, 2012 at 11:12 PM

Yes, I only quoted the relevant bit for you : ) that's the old version you've posted and it has zero logic in the Display method, everything else is changed. On your version of Orchard all the list rendering is in the Controller.

Editor
Jan 23, 2012 at 11:16 PM
OK. So sorry to sound like I am an idiot here, but I just have not done much work with Drivers/Controllers. Can I just create a Controllers folder in my theme and copy ItemController.cs in there? I tried this and edited this line:

list.AddRange(pageOfItems.Select(item => _contentManager.BuildDisplay(item, "Summary")));

to be

list.AddRange(pageOfItems.Select(item => _contentManager.BuildDisplay(item, "ContainerSummary")));

So I could now target my shape with something like Content-Employee.ContainerSummary.cshtml? I was confused on your comment to just put a driver file in my theme and paste that code in there. It seemed like it would break a lot of things.

Thanks

Arra

On Mon, Jan 23, 2012 at 6:12 PM, randompete <notifications@codeplex.com> wrote:

From: randompete

Yes, I only quoted the relevant bit for you : ) that's the old version you've posted and it has zero logic in the Display method, everything else is changed. On your version of Orchard all the list rendering is in the Controller.

Read the full discussion online.

To add a post to this discussion, reply to this email (orchard@discussions.codeplex.com)

To start a new discussion for this project, email orchard@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe on CodePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at CodePlex.com




--
Thanks,

Arra

Jan 23, 2012 at 11:31 PM

If you just copy the controller, you also need to modify the content item's DisplayRouteValues to point to your controller.

What I suggested was implementing a driver, and that's what you need to do. Copy the code pasted (i.e. the "new version" of containers) into your new driver, the old one will continue to perform all the other logic, your new one will merely render your additional JumpLinks. It's fine to have more than one driver for an item, you don't have to override all the methods.

Editor
Jan 23, 2012 at 11:57 PM

OK. I put a file in my theme called Drivers\ContainerPartDriver.cs

With this code. I am only overriding the display method here. 

protected override DriverResult Display(ContainerPart part, string displayType, dynamic shapeHelper)
        {

            Combined(
                ContentShape("Parts_Container_Contained",
                             () => {
                                var container = part.ContentItem;

                                IContentQuery<ContentItem> query = _contentManager
                                .Query(VersionOptions.Published)
                                .Join<CommonPartRecord>().Where(cr => cr.Container.Id == container.Id);

                                var descendingOrder = part.OrderByDirection == (int)OrderByDirection.Descending;
                                query = query.OrderBy(part.OrderByProperty, descendingOrder);
                                var metadata = container.ContentManager.GetItemMetadata(container);
                                if (metadata != null)
                                    _feedManager.Register(metadata.DisplayText, "rss", new RouteValueDictionary { { "containerid", container.Id } });

                                var pager = new Pager(_siteService.GetSiteSettings(), part.PagerParameters);
                                pager.PageSize = part.PagerParameters.PageSize != null && part.Paginated
                                                ? pager.PageSize
                                                : part.PageSize;

                                var pagerShape = shapeHelper.Pager(pager).TotalItemCount(query.Count());

                                var startIndex = part.Paginated ? pager.GetStartIndex() : 0;
                                var pageOfItems = query.Slice(startIndex, pager.PageSize).ToList();

                                var listShape = shapeHelper.List();
                                listShape.AddRange(pageOfItems.Select(item => _contentManager.BuildDisplay(item, "ContainerSummary")));
                                listShape.Classes.Add("content-items");
                                listShape.Classes.Add("list-items");

                                return shapeHelper.Parts_Container_Contained(
                                    List: listShape,
                                    Pager: pagerShape
                                );
                            },
                ContentShape("Parts_Container_Contained_Summary",
                             () => shapeHelper.Parts_Container_Contained_Summary(ContentPart: part)),
                ContentShape("Parts_Container_Contained_SummaryAdmin",
                             () => shapeHelper.Parts_Container_Contained_SummaryAdmin(ContentPart: part))
                );

        }

 

It is not hitting my breakpoint in the debugger so I dont think it is properly getting picked up. Does all this seem right?

Jan 24, 2012 at 1:30 PM

You need to add Placement for it (by default nothing is displayed, unless you specify a default location).

Editor
Jan 24, 2012 at 2:20 PM

Okkk I am confused because I thought placement.info was a way to customize what appears and where. But if there is no entry it should still show up.

What would my entry look like? I only have entries that limit what gets shown, not adds things in.

<Match DisplayType="ContainerSummary">   

<Place Parts_RoutableTitle="Nowhere" Parts_Common_Metadata="Nowhere"/> 

</Match>

Jan 24, 2012 at 2:35 PM

It's a mechanism to tell Orchard where to place things. If you don't tell it, it can't know, so it has to assume you don't want it. In the ContainerPartDriver example, you're emitting three shapes - Parts_Container_Container, Parts_Container_Container_Summary, Parts_Container_Container_SummaryAdmin. You don't want all of them displaying by default, so you have to tell it.

As to fixing your example:

a) "Nowhere" is a bad thing to use in any case. "-" is the null zone, and will ensure that driver code does not run. If you send shapes to a named zone then the driver code will run and the shape gets created, even though that zone doesn't exist in your template so doesn't get rendered. From a performance perspective, you should always use "-" if you're hiding a shape.

b) To send a shape to a zone, use the name of the zone. If you want to position it, use "ZoneName:5" where 5 is the position.

Surely you've seen this kind of thing in the comments in the example Placement.infos?

 

Editor
Jan 24, 2012 at 3:00 PM

I have seen this before, but I have never used it to insert shapes dynamically. In my mind all the content that I want to put into a page would be done via the Dashboard and widgets. I am trying to insert a list of employees at the top of a page and just modify the shapename to ContainerSummary. I did not know I then needed to insert the shape into a zone. Isn't this handled by me just assigning the widget to this zone in the dashboard? I added this snippet of code to my placement.info and still nothing. I expect that once we get this working, container widget will just render a new DisplayType of ContainerSummary as opposed to Summary so I can easily target it via template.

  <Match Path="/key-professionals">
    <Place Parts_Container_Contained="BeforeContent:5"/>
  </Match>

Jan 24, 2012 at 3:13 PM

As I said already, you can't just use Placement to push shapes to Layout zones. Well you can now, since I wrote a tutorial on how to do it: http://downplay.co.uk/tutorials/clay/part-2-through-the-looking-glass - but it involves adding that special handling. BeforeContent is part of the Layout, not part of the Content template, so you'd need to do that.

However yes, widgets do render in layout zones, but you set the zone thru the widgets UI, not through placement.

In your case you need to generate a custom rendered list, and you have to do that from a driver. Whether you do it from the widget's driver or the content's is up to you, but doing it in the content's driver means you don't need a widget, i.e. easier admin.

When you generate a shape from a driver, you can either return it in a DriverResult like normal, meaning it's at the mercy of placement. Or you can push it straight into the layout using the technique I described earlier: 

Services.WorkContext.Layout.BeforeContent.Add(...)

 

Editor
Jan 24, 2012 at 6:11 PM

@randompete

I really appreciate your help here. I feel like I am really lost on this topic and am trying to do something that many Orchard users would like to know how to do. Is there anyway we can do a screencast of this and you can go through the steps? I would write a blog entry afterwards explaining the process we go through so everyone could learn about this topic of overriding drivers and rendering shapes differently in different zones on the same page. Any thoughts on this? I understand your time is limited but I think it would be worth it. 

Thanks

Coordinator
Jan 24, 2012 at 8:31 PM

If you can grab a reference to a shape, you can call Display on it multiple times.

Jan 25, 2012 at 12:42 AM

He's trying to render the shape differently each time, calling Display again would just render the same shape.

Coordinator
Jan 25, 2012 at 12:45 AM

OK then, I guess I don't understand what that means. You can modify the shape between calls, for example the display type or alternates.

Jan 25, 2012 at 1:30 AM

It's a Container list, he needs to modify the rendering of each individual item in that list.

Coordinator
Jan 25, 2012 at 1:38 AM

My fault for not reading the whole thread. It's the title that threw me off.

Editor
Jan 25, 2012 at 1:43 PM

@bertrand/@randompete, what do you think of either a screencast or a demo of this? I would be happy to wrap it all up in a blog for everyone. I think it is an important topic and it just has been to hard to follow in this thread what I need to do or if it is the right solution. Having the same content type listed twice and in a different format on the same page is something people may want to do. (JumpTO Links). At this point it seems JavaScript is a better option for this scenario and I would like to see if it is possible if Orchard can get it done.

Thanks, Arra

Jan 25, 2012 at 1:59 PM

Javascript doesn't at all sound like a good solution here :)  (Jumplinks are supposed to be an accessibility feature, right?)

I think primarily you seemed to be getting confused about how Drivers and Placement work, not particularly anything to do with what we're discussing here, although yes I'm sure this kind of requirement is common enough.

Have you for instance read the basic Orchard documentation on creating a content part: http://docs.orchardproject.net/Documentation/Writing-a-content-part

Also the one on writing a Widget: http://docs.orchardproject.net/Documentation/Writing-a-widget (hint: it's basically the same thing)

Those might clear some things up. Once you understand drivers and placement better, and how widgets are just more of the same, it will seem obvious enough how you can just add an extra driver to duplicate some rendering logic.

Editor
Jan 25, 2012 at 2:28 PM

Yes. And rendering them on the client after the page loads would be a viable solution but not ideal. It seems just adding the file ContainerPartDriver.cs to my Themes\MyTheme\Drivers folder is not working anyways. Shouldn't this be picked up automatically? The debugger is not hitting it. This is the first issue I would say.

Jan 25, 2012 at 2:31 PM

- Have you changed the namespace of the driver so it doesn't conflict with anything else?

- Does your theme have a .csproj file (they don't always, it depends whether you specified it during code generation)

You should maybe create a separate "JumpLinks" module for this anyway so you can reuse it and it's not tied to your theme.

Regarding Javascript; HTML generated on the client via Javascript isn't accessible. You can never be certain whether Javascript is enabled or not. You should never rely on Javscript to generate something that you expect to always be there.

Jan 25, 2012 at 2:39 PM

BTW; where are you setting the breakpoint? Place it on the "Combined" command to see if it's actually getting picked up. The rest of the code will only run if Placement matches, so it could still be a problem with placement.

Editor
Jan 25, 2012 at 10:10 PM

I got my csproj in the theme folder along with a web.config. It has included the Drivers\ContainerPartDriver.cs file. Here is basically what is in my ContainerPartDriver.cs file, which I just copied from Core/Containers/Drivers (A unqiue namespace, unique classname has been added.) I am basically just trying to get Orchard to pickup the new file. It is not hitting my breakpoint in the file. How does Orchard find these new files in the Drivers dir?

namespace Themes.MyNamespace {
    public class MyContainerPartDriver : ContentPartDriver<ContainerPart> {
        private readonly IContentDefinitionManager _contentDefinitionManager;

        public ContainerPartDriver(IContentDefinitionManager contentDefinitionManager, IOrchardServices orchardServices) {
            _contentDefinitionManager = contentDefinitionManager;
            Services = orchardServices;
            T = NullLocalizer.Instance;
        }

        public IOrchardServices Services { get; private set; }
        public Localizer T { get; set; }

        protected override DriverResult Display(ContainerPart part, string displayType, dynamic shapeHelper) {
            return Combined(
                ContentShape("Parts_Container_Contained",
                             () => shapeHelper.Parts_Container_Contained(ContentPart: part)),
                ContentShape("Parts_Container_Contained_Summary",
                             () => shapeHelper.Parts_Container_Contained_Summary(ContentPart: part)),
                ContentShape("Parts_Container_Contained_SummaryAdmin",
                             () => shapeHelper.Parts_Container_Contained_SummaryAdmin(ContentPart: part))
                );
        }

Editor
Jan 25, 2012 at 10:37 PM

OK so I solved my problem this way. It seems to be working pretty well:

1. Created my template file for my content type summary view rendering: Content-Employee.Summary.cshtml

2. In order to create my JumpToLinks at the top of the page for all the below entries, I placed a Container Widget in the BeforeContent zone. I then override this widgets list rendering template. Parts.ContainerWidget-url-key-professionals.cshtml (Just for this URL!)

3. Inside this template I am inserting an alternate for my widget list on this page. See code:

@using Orchard.ContentManagement
@using Orchard.DisplayManagement.Shapes
@{
    var list = Model.ContentItems;
    var items = list.Items;
    Model.ContentItems.Classes.Add("content-items");
    Model.ContentItems.Classes.Add("list-items");
    
    foreach(var item in items)
    {
        ShapeMetadata metadata = item.Metadata;
        string alternate = metadata.Type + "_" +
                metadata.DisplayType + "__" +
                item.ContentItem.ContentType +
                "_JumpLink";
        metadata.OnDisplaying(ctx =>
        {
            metadata.Alternates.Add(alternate);
        });
    }
}
@Display(Model.ContentItems)

4. Lastly, I am now allowed another two alternates which I found via ShapeTracer which differentiate from my Content Type's summary template:  So I created this template, Content-Employee.JumpLink-url-key-professionals.Summary.cshtml

Now I have a template for my JumpTo links for my employee type and for my summary rendering on the same page. Thoughts!? Works like a champ. 

Jan 25, 2012 at 11:12 PM

Regarding the driver problem - the shapes you're returning there won't work, you haven't got the rendering logic.

However, the method you've used certainly works, and whilst at first glance I thought it looked a bit strange, manipulating the alternates in that way isn't actually a bad idea; you haven't had to duplicate any code, and it's taking advantage of a simple feature of Orchard's templating framework. So yes, I would go with that!

Editor
Jan 25, 2012 at 11:18 PM

Yes and also it only manipulates list widgets on that page because we are using a url alternate too. So it is probably less invasive than the original solution. I would however like to know how to override a driver in a theme so I can do this in the future. I know I don't have the rendering logic, but I am just trying to hit the breakpoint right now to make sure it is getting picked up. Any thoughts on why?

Jan 25, 2012 at 11:37 PM

Did you try setting a breakpoint in the constructor to see if it's even getting instantiated?

Editor
Jan 26, 2012 at 12:02 AM

Yea its not :( Can you try and dup this on your system?

Jan 26, 2012 at 1:22 AM

Can you try creating a new module and add the driver there; then enable the module and see if your breakpoint still isn't hit.

I found it a bit strange before getting dependencies to load from a theme, maybe there's a general problem with doing things this way.