Adding Alternates to Container Widgets Shape (list)

Topics: Customizing Orchard, Writing modules, Writing themes
May 11, 2011 at 5:35 PM

Hi to everyone,

I've managed to add alternates to my Container Widget shape through code, implementing the IShapeTableProvider interface like this:

public class ShapeProvider : IShapeTableProvider
	{
		private readonly IWorkContextAccessor _workContextAccessor;

		public ShapeProvider(IWorkContextAccessor workContextAccessor)
		{
			_workContextAccessor = workContextAccessor;
		}

		#region Implementation of IShapeTableProvider

		public void Discover(ShapeTableBuilder builder)
		{
			builder.Describe("Parts_ContainerWidget")
				.OnDisplaying(displaying =>
								{
									var items = displaying.Shape.ContentItems.Items as IList<dynamic>;
									if (items != null && items.Any()) // this is a list and it is not empty
									{
										string contentType = items.First().ContentItem.ContentType;
										if (!String.IsNullOrWhiteSpace(contentType)) {
											displaying.ShapeMetadata.Alternates.Add("Parts_ContainerWidget__" + contentType);
											displaying.ShapeMetadata.Alternates.Add("Parts_ContainerWidget__" + contentType + "__" + displaying.ShapeMetadata.DisplayType);
										}
									}
								});
		}

		#endregion
	}

Now I'd like to add some alternates that reflect the "Title"of the list to differentiate between a list and another. I know I should access the list ContentItem and get the "Title" filed but I cannot find a way to do it.

Some Ideas?

Thanks in advance

May 11, 2011 at 5:40 PM

It'd normally be: displaying.Shape.ContentItem.RoutePart.Title

May 12, 2011 at 9:42 AM

Thanks for your reply. I've tried it but it doesn't work. I think that during the widget creation the only property created for the list is "ContentItems". Maybe I could access the ContentItem in the "list" shape but not in the "Container Widget" shape.

I'm getting lost with this shapes, there is no way to find properties/methods though intellisense, and documentation is very difficult to find out.

I've installed Shape tracing tool to look at the shapes and models, and pointing to my "Parts_ContainerWidget" in the model I have only ContentItems.

Trying to look at the code generating part I've seen this in tre ContainerWidgetPartDriver:

        protected override DriverResult Display(ContainerWidgetPart part, string displayType, dynamic shapeHelper) {
            return ContentShape(
                "Parts_ContainerWidget",
                () => {
                    var container = _contentManager.Get(part.Record.ContainerId);

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

                    var descendingOrder = part.Record.OrderByDirection == (int)OrderByDirection.Descending;
                    query = query.OrderBy(part.Record.OrderByProperty, descendingOrder);

                    if (part.Record.ApplyFilter)
                        query = query.Where(part.Record.FilterByProperty, part.Record.FilterByOperator, part.Record.FilterByValue);

                    var pageOfItems = query.Slice(0, part.Record.PageSize).ToList();

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

                    return shapeHelper.Parts_ContainerWidget(ContentItems: list);
                });
        }

The red line should be the one responsible to create the part and it creates only "ContentItems". Maybe something like this should do the trick: 

return shapeHelper.Parts_ContainerWidget(ContentItem:container, ContentItems: list);

I wonder if there is a way to inject this property to the part after creation?

May 12, 2011 at 10:19 AM
Edited May 12, 2011 at 10:20 AM

Ok, this is the same problem as with default list display (from the ItemController). For whatever reason the decision was at some point taken that people would only ever need the list items, not the container itself!

I know the devs have it in mind to do something about this for future release.

Now ... depending on exactly what you are doing with the container, you could try using Mechanics. It's a module I've written that gives you a many-to-many relationships system that goes well beyond the capabilities of lists.

Instead of anything akin to ContainerWidget, instead Mechanics has a feature called Paperclips which allows you to push different types of connector to different zones. So this is kind of a replacement for the "page layer".  Or by attaching content to the Site object, you can cause its connections to be rendered on every page. By attaching content to the User object, connections will be displayed in zones just for that user. You can also hook into events to provide your own rules for how shapes are dispatched to zones. So it has the flexibility of the layers system, it's slightly more work to set things up, but better UI in the long run.

If that system sounds a bit too much for you (bearing in mind it's still pretty new and there are certain things still needing some improvement!) you could try using an IShapeTableProvider to modify the "Parts_ContainerWidget" shape and add the content item into the shape model, assuming it's even there.

On the other hand if you want to use Mechanics I'm happy to help you get the rendering how you need it. I just double checked and I wasn't actually passing in the ContentItem yet (on the Socket shape which is the equivalent here), but I've just fixed that and will push the change up to the gallery and codeplex site soon.

Edit: BTW, gallery page is http://www.orchardproject.net/gallery/List/Modules/Orchard.Module.Downplay.Mechanics

May 12, 2011 at 10:56 AM

I've managed to get the Title using this approach on my IShapeTableProvider implementation:

 

builder.Describe("Parts_ContainerWidget")
	.OnDisplaying(displaying =>
					{
						var items = displaying.Shape.ContentItems.Items as IList<dynamic>;
						if (items != null && items.Any()) // this is a list and it is not empty
						{
							var item = items.FirstOrDefault();
							if (item == null) return;
							string contentType = item.ContentItem.ContentType;
							string containerName = item.ContentItem.CommonPart.Container.RoutePart.Title;
							displaying.ShapeMetadata.Alternates.Add("Parts_ContainerWidget__" + contentType);
							displaying.ShapeMetadata.Alternates.Add("Parts_ContainerWidget__" + containerName);
						}
						else
						{
							displaying.ShapeMetadata.Alternates.Add("Parts_ContainerWidget__empty");
						}
					});
I've seen your Mechanics and it seems to provide a high livel of flexibility (putting content in arbitrary zone is super-duper useful).
I'd like to getting deeper on this with a little help from you if you wish to.

 

May 12, 2011 at 11:34 AM

Ah yeah, that would work. That's the nice thing about Orchard, there are so many ways to work around problems!

With Mechanics what I'm doing is providing the the simplest building blocks I possibly can, and then building a number of "Theory" projects that implement common user scenarios, these are intended as templates, examples or starter kits to build up from. These are also then directing the core features I need to provide. The current Theory projects are "Economics" which is a basic webshop system with Products, Categories, and Related Products; and then "Cartography" which is a hierarchical menu system using menu connectors. These can both be got from source code on the Codeplex site (http://scienceproject.codeplex.com). I'm also just working on the per-item permissions feature ("Quanta: Effectors") which adds Groups as well as connectors for User->Content, User->Group, Group->Content.

There are also some simple walkthrough examples in the documentation on Mechanics and Paperclips, which are just describing the steps to start building the webshop infrastructure using the UI.

Something else that would be useful for your situation is I already provide Alternate names based on the connector type, as well as the content types at either end of the connector. I'm thinking that using Title as an alternate wouldn't be a common need; but on the other hand you could easily achieve the same effect just by creating different types of connector for different rendering types. You can also change the DisplayType using the Paperclips part (and actually per connector as well) so there are loads of ways to have custom rendering.

I'm happy to help in any way with this; having people use the system will help drive the features that need implementing, as well as work out what needs clarifying for documentation. Also I'm really hoping to find other people who can help with documentation, walkthroughs, examples etc. as it's all quite time consuming, and sidetracking me from finishing up some of the more advanced features I've got planned! It's already pretty powerful as it is but there is more to come :)

May 12, 2011 at 12:01 PM
Edited May 12, 2011 at 12:02 PM

I've installed Mechanics and followed the short "Example" you privide in this page: http://scienceproject.codeplex.com/wikipage?title=Mechanics

All seems to be configured well but when I view a Category I cannot see the related products.

Using Shape tracing I cannot see parts related to the socket or the connector.

If you want more details and maybe screenshot and other we can move somewhere else and open a thread there. Let me know.

May 12, 2011 at 12:08 PM

Sorry about that - you just need to add this in placement:

  <Place Parts_Sockets="Content:10" />

It'll be fixed in the next changes I upload!

May 12, 2011 at 12:30 PM

That's fixed in the new version on the gallery (0.9.2).

We can take further discussion to the Science Project site if you wish.