Accessing Model.ContentItem from IShapeTableProvider

Topics: Customizing Orchard, General, Troubleshooting
Mar 31, 2011 at 3:15 PM

Bit of a long story why I'm trying to do this - but something pretty awesome if it works!

I've got a Content Part that displaying fine from this Driver:

            results.Add(
                    ContentShape("Parts_Foo_Bar",
                    () => shapeHelper.Parts_Foo_Bar(
                        ContentPart:part,
                        ContentItem: part.ContentItem),
                        ContentItems:list)));

...

return Combined(results.ToArray());

So ContentPart, ContentItem, ContentItems can all be found from my view by accessing @Model.ContentItem etc.

Now say I write the following class in another module:

    public class Shapes : IShapeTableProvider
    {
        public void Discover(ShapeTableBuilder builder)
        {
            builder.Describe("Parts_Foo_Bar")
                .OnCreated(OnCreated);
        }
        private void OnCreated(ShapeCreatedContext context)
        {
            ContentPart part = context.Shape.ContentPart;
            ContentItem item = context.Shape.ContentItem;
            if (item == null || part == null) throw new Exception("No item or part");

        }
    }

What I'm finding is that Shape.ContentPart and Shape.ContentItem are null; and I can't find any way to access the Model that will end up in the view. I want to add a wrapper at this stage based on certain properties of the part but I can't find it.

Any clues on this?

Mar 31, 2011 at 5:37 PM

I don't understand why you create the class Shapes.cs? And how do you make use of it?

Mar 31, 2011 at 5:45 PM

IShapeTableProvider is used in Core.Shapes and other places to describe properties about a whole bunch of the shapes that you normally see on an Orchard page.

I want to use it to add a Wrapper around my Parts_Foo_Bar (obviously I have simplified an example from my real application, I don't really have a Parts_Foo_Bar, but I want to be able to do this with any part).

But I can't add the Wrapper until I've checked settings from my part. However I can't *find* the part because both Shape.ContentPart and Shape.ContentItem are null. I'm wondering how I can access the model that I created in my Driver.

Coordinator
Mar 31, 2011 at 5:49 PM

This code is going to run for *all* shapes. My guess would be that this does throw, but not for your shape. You just need to verify that you have the right shape.

Coordinator
Mar 31, 2011 at 6:28 PM

Can you try using the brand new shape.Metadata.OnDisplay(Action) ?

It's called just before the HTML gets rendered, and you can even return your own HTML, to do some caching for instance.

Mar 31, 2011 at 6:44 PM

bertrand:

Surely it will only run for "Parts_Foo_Bar" - isn't that the point of builder.Describe? (I verified this in context.Shape.Metadata.Type)

I also tried using implementing IShapeFactoryEvents (which I think is what you're thinking of) but got the same result with this code (i.e. contentItem==null) :

   public void Created(ShapeCreatedContext context)
        {
          
            if (context.ShapeType=="Parts_Foo_Bar")
            {
                
                ContentItem contentItem = Context.Shape.ContentItem;
                if (contentItem != null)
                {
                    if (contentItem.As<FooPart>().Bar) {
                        var shapeMetadata = (ShapeMetadata)context.Shape.Metadata;
                        shapeMetadata.Wrappers.Add("FooBarWrapper");
        
                        
                    }
                }
            }
        }

sebastien:

I didn't know about that, will give it a try - but can you explain what the difference is between ShapeMetadata.OnDisplaying, IShapeDisplayEvents.Displaying, and IShapeTableProvider => build.Describe("...").OnDisplaying(...) . These look like three different ways to hook into the same event. I've already tried it from IShapeDisplayEvents and IShapeTableProvider, still no access to ContentItem.

 

However - I'm pretty sure this is actually the correct result, I'm just trying to access it in the wrong way ... Shape Tracing confirms the Shape itself has no ContentItem property, it's on the Model tab instead ... but I want to know where can I access the model at a point where I can also add a Wrapper?

Thanks for both your help so far !

Mar 31, 2011 at 6:51 PM

Ok - in ShapeMetadata.OnDisplaying it works. So ... why does it work there and not in the other places?

Coordinator
Mar 31, 2011 at 7:29 PM

My bad. Why it doesn't work in other places? I don't know, too early probably, but it does seem to make the event pretty much useless. Asking around.

Coordinator
Mar 31, 2011 at 7:41 PM

The the shape Creating/Created events are really to build a named shape before any specific information is available. You can think of those events as firing right before and right after a parameterless constructor.

So the best use for those two events is if you want to change a shape's base class (on creating) or attach shape-specific system-wide behaviors (on created) before any method invokation or property assignment occurs.

OnDisplaying, on the other hand, fires after all of the dust is settled and you're just about to render. That moment gives you the greatest possible chance you'll be able to work with the final values that all of the different bits of code have thrown onto the shape, no matter when or how they occured.

One last observation - if you really want to jump into the shape from the beginning - is that in the OnCreating (or created) events for a shape you can attach an additional behavior. That behavior can then see all of the property assignments and method invocations as they occur - so from that standpoint you can have code that adds or updates a wrapper template when the ContentItem property is assigned.

It's very flexible. :)

 

Mar 31, 2011 at 7:54 PM

Thanks ... I  think I follow it now. It's just sometimes really difficult to see what's going on when all the important bits are dynamic objects that you can't really look into!

So would it therefore be possible in one of the creating hooks to push a shape into a different Zone?

Coordinator
Mar 31, 2011 at 9:36 PM

Oh yeah. I have a module that does that. Never found the time to finish it but yeah.

Mar 31, 2011 at 9:43 PM

I think that's a nice place to do it - I couldn't manage it in a Handler as you suggested in that other thread, I ended up just creating my own Driver for part I wanted to move, building custom shapes and hiding the normal ones. But it could be a bit of a pain where shapes have a more complicated Display method.

I think an ideal thing would be to just introduce it into Placement.info at some stage, so you can use <Zone Shape_Name="Header"/>. Eventually :)