Customizing RecentBlogPosts Widget

Topics: Customizing Orchard, Troubleshooting, Writing modules, Writing themes
Sep 6, 2011 at 10:08 AM
Edited Sep 6, 2011 at 10:49 AM

Hi,

here is a situation. I want to take control over RecentBlogPosts Widget rendering... Sounds fairly easy. Shape tracer makes me an override for Widget-RecentBlogPosts.cshtml, excellent.

But here is a pickle! My blog posts have a custom part on em, called UberBody, and that uber body consists of 2 text elements etc. So, I'd specifically like to be able to display something called "Lead" that is a part of my UberBody custom part.

I'll do some pseudo-code...

 

@foreach (var item in blogPosts)
{
     <p>@item.UberBody.Lead.Value();</p>
     @Html.ItemDisplayLink(item.Title as String, item.ContentItem as ContentItem)
}

 

something like that.

This won't work surely, but you should be able to get what i'm trying to do. 

I remember seeing something similar on Bertrand's blog but I can't find that sodding thing :)

Anyway. Help would be appreciated!

Cheers!

Sep 6, 2011 at 12:15 PM
Edited Sep 6, 2011 at 12:16 PM

Using the As<TPart>() extension method you can access a part of a content item, e.g.:

item.As<UberBodyPart>().Lead.Value();

You can also do this if you have a part and wish to access another part under the same content item, e.g. if you had a list of UberBody parts and wanted to access the RoutePart:

uber.As<RoutePart>().Title

If it doesn't work first time, you might need to add a reference to Orchard.ContentManagement at the top of your view:

@using Orchard.ContentManagement

Sep 6, 2011 at 3:59 PM

You can actually reference the parts of the content item without using the As<> extension method. If you just treat the content item as a dynamic, you can access the parts like:

@foreach (dynamic item in blogPosts)
{
    <p>@item.UberBody.Lead</p>
}
This is assuming that the items in blogPosts are the content items. The point is that once you have a dynamic content item reference, you can just access the parts and the properties on those parts via normal dot notation.

Sep 9, 2011 at 1:46 PM

Thanks kobowi and kevink.

I did it similar to kobowi's suggestion. 

Anyway I'm stuck with a new problem now. It's not directly related (or is it?) to this.

Here's the deal... My BlogPost contentType no longer has default BodyPart, it now uses custom UberBodyPart.

Now i'm getting a error when displaying posts (not a blogPost lists, just when displaying single full BlogPost)

Error appears in model of BlogPost. Message is... "Object reference not set to an instance of an object."

Here:

 public string Text {
            get { return this.As<BodyPart>().Text; }
            set { this.As<BodyPart>().Text = value; }
        }

I didn't have much time to get into framework and how stuff works when you remove a certain Part from contentType.

So, point me into right direction please.

Here's a full stack trace, that i didn't find all that helpful :)

 	Orchard.Blogs.dll!Orchard.Blogs.Models.BlogPostPart.Text.get() Line 21 + 0x14 bytes	C#
 	[External Code]	
>	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpMember(object o, System.Reflection.MemberInfo member) Line 191 + 0x16 bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpMembers.AnonymousMethod__1() Line 121 + 0x29 bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.SafeCall(System.Action action) Line 233 + 0xb bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpMembers(object o) Line 121 + 0x3e bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpObject(object o, string name) Line 97 + 0xb bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.Dump(object o, string name) Line 54 + 0xe bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpMembers.AnonymousMethod__2() Line 133 + 0x40 bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.SafeCall(System.Action action) Line 233 + 0xb bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpMembers(object o) Line 133 + 0x3e bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpObject(object o, string name) Line 97 + 0xb bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.Dump(object o, string name) Line 54 + 0xe bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpShape(Orchard.DisplayManagement.IShape shape) Line 175 + 0x24 bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpObject(object o, string name) Line 86 + 0x17 bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.Dump(object o, string name) Line 54 + 0xe bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpShape(Orchard.DisplayManagement.IShape shape) Line 175 + 0x24 bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpObject(object o, string name) Line 86 + 0x17 bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.Dump(object o, string name) Line 54 + 0xe bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpShape(Orchard.DisplayManagement.IShape shape) Line 175 + 0x24 bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.DumpObject(object o, string name) Line 86 + 0x17 bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ObjectDumper.Dump(object o, string name) Line 54 + 0xe bytes	C#
 	Orchard.DesignerTools.dll!Orchard.DesignerTools.Services.ShapeTracingFactory.OnDisplaying(Orchard.DisplayManagement.Implementation.ShapeDisplayingContext context) Line 99 + 0x1d8 bytes	C#
 	Orchard.Framework.DLL!Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Execute.AnonymousMethod__4(System.Action action) Line 72 + 0x11 bytes	C#
 	Orchard.Framework.DLL!Orchard.InvokeExtensions.Invoke>(System.Collections.Generic.IEnumerable> events, System.Action> dispatch, Orchard.Logging.ILogger logger) Line 19 + 0xe bytes	C#
 	Orchard.Framework.DLL!Orchard.DisplayManagement.Implementation.DefaultDisplayManager.Execute(Orchard.DisplayManagement.Implementation.DisplayContext context) Line 72 + 0x5e bytes	C#
 	Orchard.Framework.DLL!Orchard.DisplayManagement.Implementation.DisplayHelper.ShapeExecute(object shape) Line 71 + 0xf bytes	C#
 	Orchard.Framework.DLL!Orchard.DisplayManagement.Implementation.DisplayHelper.Invoke(string name, ClaySharp.INamedEnumerable<object> parameters) Line 38 + 0x2d bytes	C#
 	Orchard.Framework.DLL!Orchard.DisplayManagement.Implementation.DisplayHelperFactory.DisplayHelperBehavior.InvokeMember(System.Func<object> proceed, object target, string name, ClaySharp.INamedEnumerable<object> args) Line 27 + 0x3b bytes	C#
 	[External Code]	
 	App_Web_jva3ledj.dll!ASP._Page_Core_Shapes_Views_ShapeResult_Display_cshtml.Execute() Line 1 + 0x294 bytes	C#
 	[External Code]	
 	Orchard.Framework.DLL!Orchard.Mvc.ViewEngines.ThemeAwareness.LayoutAwareViewEngine.FindView.AnonymousMethod__5(System.Web.Mvc.ViewContext viewContext, System.IO.TextWriter writer, System.Web.Mvc.IViewDataContainer viewDataContainer) Line 59 + 0x1f bytes	C#
 	Orchard.Framework.DLL!Orchard.Mvc.ViewEngines.ThemeAwareness.LayoutAwareViewEngine.LayoutView.Render(System.Web.Mvc.ViewContext viewContext, System.IO.TextWriter writer) Line 90 + 0x1d bytes	C#
 	[External Code]	
</object></object></object>

Help me out :)

Cheers!

Sep 9, 2011 at 2:14 PM

It sounds it's still trying to render the Body part. Are you sure you've got it removed from the BlogPost content type? Try adding this to your Placement.info in your theme:

<Match ContentType="BlogPost">
    <Place Parts_Common_Body="-"/>
</Match>

Sep 9, 2011 at 2:19 PM

In this case the Orchard.Blogs module has been written to rely on the BodyPart, which is why it is failing (specifically, As<>() returns null if it cannot locate a part of the given type on the relevant content item, hence the NullReferenceException), so you shouldn't remove the BodyPart from this content item.

I think you will have to make do with leaving the BodyPart on the Blog type, and hiding it on display/editor etc via placement.info.

Sep 9, 2011 at 2:32 PM
Edited Sep 9, 2011 at 2:33 PM

@kevink: I think it's because Orchard.Blogs.Models.BlogPostPart directly references BodyPart:

 

public string Text {
    get { return this.As<BodyPart>().Text; }
    set { this.As<BodyPart>().Text = value; }
}

 

Although 'Find All References' in VS shows that it is only referenced in the XML RPC code, unless it is being called from a view somewhere.

Sep 9, 2011 at 2:55 PM

Thanks again both of you.

@kobowi, well that's exactly it.... BodyPart is no longer in 

((((Orchard.ContentManagement.ContentPart)(this))).ContentItem)._parts

As I've removed it from BlogPost Content Type.

Denying rendering in placement.info doesn't help, well not without putting that part back on the content type.
I'm not keen on doing that, for now.

Anyone did anyone else try removing body part from blogpost?
I cannot shake the feeling that I did something wrong, maybe Bertrand can shed some light on this.

Anyway I'll try to dig a bit around to see if I can circumvent this somehow.

Sep 12, 2011 at 3:56 PM

I've put this matter to a rest... I wont share the method i used to circumvent this because it's bad practice and should never be used :)

Now on a related note.

I've also added a field to my custom contentType (RecentWork is the name of that custom content type). Field is called TeaserPicture type text. Ok, so I need to get the value of my specific field.

var url = ((IContent)Model.ContentItem).As<ContentPart>().Fields.Where(f => f.Name == "TeaserPicture").FirstOrDefault();

Problem here is (I'm assuming) there are two two parts of type ContentPart, first one is something related to versioning (not sure) and that one does not contain any fields, so my variable will be... well, null.
Other part, the one that I need has fields and should return something. So, what is the general approach on accessing fields on custom ContentTypes?

Thanks!

Sep 12, 2011 at 5:24 PM

When you create a custom content type in the dashboard, Orchard adds a ContentPart with the same name as the type. And that's the part that the fields get attached to.

By treating things as a dynamic type you can access the parts and fields easily as if they're properties on the object graph. Since the Model is a dynamic object you can simply do this:

var url = Model.ContentItem.RecentWork.TeaserPicture;

Sep 16, 2011 at 10:37 AM

Thanks kevink!

Sep 16, 2011 at 2:44 PM

Hi.

How about this one...

I have a ContentType named Portfolio that is actually a container of contentTypes RecentWork.

And I'm trying to modify a view that would display that portfolio which is a list of RecentWork items, specifically Content-Portfolio.cshtml

I've looked a bit into model structure and found RecentWork items in 

Model.Content.Items[1].Items, I sincerely doubt that this is the way I'm supposed to get to those.

Here's my idea in pseudo-code

var portfolio = Model.Portfolio.Items

foreach (dynamic item in porftolio)
{
     <p>@item.TeaserPicture</p>
}

Any suggestions?

Sep 16, 2011 at 3:01 PM

If I'm reading that right, the item in your foreach loop is now a RecentWork content item. So just like in the previous example you would access:

<p>@item.RecentWork.TeaserPicture</p>

Sep 16, 2011 at 3:07 PM

Ok, that is answer to one part.

but prior to foreach part, variable portfolio needs to be populated with actual items.

var portfolio = Model.Portfolio.Items

I don't think this works actually.

Does it?

Sep 16, 2011 at 3:32 PM

You said before you found the RecentWork items where in Model.Content.Items right? Did you try that? I suspect that's what it is if you're in the template for the Portfolio content item which is a container for RecentWork content items.

Sep 19, 2011 at 9:47 AM

Hey kevink,

Actual items are located in  Model.Content.Items[1].Items...

Let me give you a complete code (feel free to ignore all HTML as it obviously isn't relevant)

@using Orchard.Utility.Extensions;
@using Orchard.ContentManagement;
@{
	
    var portfolio = Model.Content.Items[1].Items;
}

    <ul class="portfolio two-columns">
    @for (int i = 0; i < portfolio.Count; i++)
    {
        dynamic item = portfolio[i];
        <li>@item.RecentWork.TeaserPicture</li>
    }
    
    </ul>

In this case... portfolio contains 4 of RecentWork items, but @item.RecentWork.TeaserPicture doesn't return a value.

Also, initializing portfolio part is what bothers me, I manually found, while debugging, that items are actually located in Model.Content.Items List item with index 1. Is that always the case?

That item Model.Content.Items[1], contains another list with actual RecentWrok items.

As you can see my understanding Clay and related concepts is, well, not all that great, so bare with me...

Thanks,

T out~

Sep 19, 2011 at 10:42 AM

Oh yeah,

looking more into that list i figured this last part out.

data i was looking for is in here item.ContentItem.RecentWork.TeaserPicture

Now the first part.... about portfolio. Using a specific List index doesn't look to smart to me, because I'm not sure if that List is going to be formed this way, every time.