How to render all the items in a Content Item's Container Part when rendering the Content Item

Topics: Writing modules, Writing themes
Jul 23, 2013 at 7:10 PM
Edited Jul 30, 2013 at 5:36 PM
I have a custom content item called ProductCategory, which has the Containable content part. This part is set to contain only Content Items of type "Product".

I am attempting to make an accordion in my theme that shows a list of all product categories, each of which can expand to show the products within that category as shown below.
- Product Category A
        Product 1
        Product 2
- Product Category B
        Product 3
        Product 4
        Product 5
+ Product Category C
+ Product Category D
Is there a way I create an alternate template for a projection of all ProductCategories that can access a list of the products within each ProductCategory?

If not, what is my best bet for accomplishing this functionality in a clean way?

Thanks in advance for any helpful advice!
Coordinator
Jul 23, 2013 at 7:58 PM
Looks like grouping in the query may be the way to go, but I haven't verified that it would work in that case.
Jul 23, 2013 at 8:12 PM
Edited Jul 23, 2013 at 8:12 PM
I've looked at that possibility, but wouldn't that simply display all Products and Product Categories in a single-level list? I need to group the Products with the Product Categories they are associated with.

Another option I've been toying with is to turn on the ItemsShown bool on the ContainerPart of the ProductCategory Content Type. That would show all the items within the container in the details view of the product category, right? I just can't figure out how to change that variable.

Seriously, thank you for your help!
Coordinator
Jul 24, 2013 at 3:36 AM
No, grouping does grouping ;) Try it.
Jul 24, 2013 at 3:03 PM
Makes sense that it would. :)

I tried the settings below in the query. Is that what you were suggesting I try, or did I misunderstand you?

Image

Thanks.
Coordinator
Jul 25, 2013 at 8:47 PM
No, that is not the grouping you're looking for. Edit the layout, it's in there if the layout is in property mode.
Jul 26, 2013 at 3:44 PM
I found the grouping you meant. However, I tried filtering by Products only, by Product Categories only, and by Products OR Product Categories, and I consistently get a message (shown below) saying no properties are currently available in order to group the view. I am still doing something wrong, or does this mean it's not possible?

Thanks again.

Image
Coordinator
Jul 27, 2013 at 1:08 AM
You need to add some properties.
Jul 27, 2013 at 4:09 PM
I was able to create a query with a filter on Products (not Product Categories) and an unordered list layout in property mode that grouped the results by a "Sort Order" ContentField attached to the ProductCategories content type.

While this did seem to sort the list of Products based on their Category, it still did not provide the functionality I'm looking for--namely the ability to display a nested list of all ProductCategories with all the relevant Products inside each ProductCategory. Am I still missing some functionality provided by the "grouping property", or is there simply not a way to do this without creating a custom module?

Thanks again for your help!
Coordinator
Jul 28, 2013 at 1:59 AM
I just tried it, grouped on creation date in the layout, and got nested ULs rendered as a result. I don't know what you've tried, but grouping definitely works and renders nested lists.
Jul 29, 2013 at 3:22 PM
I really appreciate your willingness to explain this. Allow me to bug you one last time.

I also tried grouping by creation date, and that worked great. It did just what you said. However, what I want to do is group by Product Category. (As I mentioned earlier, ProductCategory has a Container content part. Product has the Containable content part. Each Product is placed in the relevant ProductCategory container.)

Here's the catch: When I go to add a new property in the layout page of my query (the page where you chose Creation Date), I do not see any property listed that would allow me to group by the Container item associated with the product, which is what I need to group by. Am I making sense?

You may wonder why I don't just add a custom field for Product Category inside the Product content type and group by that. I don't want to do this because for this website, Product Categories are objects. I couldn't simply use a taxonomy for them, because they have their own pictures, special fields, and templates.

I hope I've made myself clear. Thanks again for your advice.
Coordinator
Jul 29, 2013 at 11:33 PM
Edited Jul 29, 2013 at 11:33 PM
Did you try to go to the Bindings tab before you tried to add the category? Only properties registered as bindings will be visible as properties in the layout, and thus can be used for grouping.
Jul 30, 2013 at 5:31 PM
Edited Jul 30, 2013 at 5:34 PM
I did. Here are bindings available for Container and Containable:

Image

I could not figure out a way use these to list the Container "ProductCategory" items with nested Containable "Product" items under each one. Perhaps I'm missing something obvious.

ID seems like the most likely candidate, but it didn't seem to work, and assuming that ID in both cases represents the unique item ID it makes sense that it wouldn't.

ItemsShown is a bool. I'm guessing when it's set to true that all the items in that container will appear in the container's detail view. If that's the case, I could just set that to true and then show a detail view of all the ProductCategory items even though it wouldn't be a clean nested list. I'm not sure how the value of that variable can be changed, though, as there isn't an option for it in the editor page for the ProductCategory content type.
Coordinator
Jul 30, 2013 at 9:48 PM
Yes, something may be missing here for containers to be used meaningfully. Let's try another approach. Would you consider writing some code to make this work? A simple controller should be able to take care of this.
Jul 31, 2013 at 2:21 PM
Yes, I would be willing to write some code to accomplish this functionality. Being new to Orchard, I am not sure where to start. If you happen to know of any tutorials or documentation that would be helpful (aside from generic here's-how-you-create-a-new-module tutorials, etc.), or have any other pointers or tips, let me know.

If I'm able to add the functionality, I'll post my solution on here for posterity. :)
Coordinator
Jul 31, 2013 at 8:16 PM
In your controller action, start by injecting IContentManager, and use Query on that (look for examples of usage throughout the code). Once you've built your query, inject a shape factory and return a shape result from the action. Don't forget the [Themed] attribute. Then you can build a view with the same name as the shape you created from the action.
Aug 1, 2013 at 7:04 PM
With the help of another developer, I got it to work. Thanks.

For others who find this page, we created a new widget module with a basic model for the widget. We then created a driver class to query for the info we needed (see below), and hooked it up with a custom template.

The guts of what we did is located within the driver class. Here is the code for that.
    public class ProductCategoriesTreeWidgetDriver : ContentPartDriver<ProductCategoriesTreeWidgetPart>
    {
        private readonly IContentManager _contentManager;

        public ProductCategoriesTreeWidgetDriver(IContentManager contentManager)
        {
            _contentManager = contentManager;
        }

        protected override DriverResult Display(ProductCategoriesTreeWidgetPart part, string displayType, dynamic shapeHelper)
        {
            var parentContentItems = new List<ParentContentItem>();

            var categories = _contentManager.Query(VersionOptions.Published, "ProductCategory").List();

            foreach (var category in categories)
            {
                var parentContentItem = new ParentContentItem();

                parentContentItem.ParentItem = category;

                parentContentItem.ChildItems = _contentManager.Query<CommonPart, CommonPartRecord>()
                    .Where(c => c.Container.Id == category.Id)
                    .List()
                    .Select(item => item.ContentItem);

                parentContentItems.Add(parentContentItem);
            }

            part.TreeData = parentContentItems;

            return ContentShape("Parts_ProductCategoriesTreeWidget",
                                () => shapeHelper.Parts_ProductCategoriesTreeWidget(
                                  TreePart: part
                                        ));
        }

...


    }
Coordinator
Aug 1, 2013 at 7:50 PM
You have a Select N+1 issue in there however. Performance will degrade with more items and categories. There should be one or two queries in there, instead of the query inside the loop.
Aug 1, 2013 at 7:54 PM
Good point. Thanks for noting that. I'll change that when I get a chance.