Displaying Container Part Items From Content Picker Field - orchardcms

I have a content type called DayTile which I have setup with a content picker field that is limited to HotelSection type. This type is a container for the Hotels type.
I want to render all the hotels in the HotelSection when the DayTile has that section picked. Is this possible?
From what I have tried so far, it does not seem that the HotelSection's contained items are accessible as a content picker field.

The selected content items are exposed by the ContentPickerFields ContentItems property (see Orchard.ContentPicker.Fields.ContentPickerField).
To access this from a DayTile content item, let's say from a Razor template named "Content-DayTile.cshtml", you would do it like so:
#{
dynamic contentItem = Model.ContentItem; // How to access the content item depends on the context. In this case, we're in a Content shape template.
// Assuming your picker field is named "HotelSections" and attached to your DayTile content type (which in reality means it's attached to an automatically created part called "DayTile").
var picker = contentItem.DayTile.HotelSections;
var hotelSectionContentItems = picker.ContentItems; // Typed as IEnumerable<ContentItem>.
}
<ul>
#foreach(var hotelSectionContentItem in hotelSectionContentItems)
{
// You can now either access individual parts and fields of each hotel section content item and render it, or you can choose to build a shape for each item and display that. Here are examples:
<li>#hotelSectionContentItem.TitlePart.Title</li>
// or:
// Build a content shape for the content item.
var hotelSectionContentShape = BuildDisplay(hotelSectionContentItem, "Summary");
// Render the shape.
<li>#Display(hotelSectionContentShape)</li>
}

If I understand you correctly this should absolutely be possible.
You will want to override the Fields.ContentPicker view in your theme/module, and in the foreach loop instead of displaying a link to each HotelSection, you want to display that HotelSection content item, which in turn will display all the Hotels contained within the HotelSection, like so:
#using Orchard.ContentPicker.Fields
#using Orchard.Utility.Extensions;
#{
var field = (ContentPickerField) Model.ContentField;
string name = field.DisplayName;
var contentItems = field.ContentItems;
}
<p class="content-picker-field content-picker-field-#name.HtmlClassify()">
<span class="name">#name</span>
#if(contentItems.Any()) {
foreach(var contentItem in contentItems) {
#Display(contentItem.ContentManager.BuildDisplay(contentItem, "Detail"))
}
}
else {
<span class="value">#T("No content items.")</span>
}
</p>
If you are looking to access the Hotels directly from within the content picker field you will need to add a little bit more to the view, a little messy but like this:
#using Orchard.ContentPicker.Fields
#using Orchard.Utility.Extensions;
#{
var field = (ContentPickerField) Model.ContentField;
string name = field.DisplayName;
var contentItems = field.ContentItems;
}
<p class="content-picker-field content-picker-field-#name.HtmlClassify()">
<span class="name">#name</span>
#if(contentItems.Any()) {
foreach(var contentItem in contentItems) {
var container = contentItem.As<ContainerPart>();
if(container == null) {
continue;
}
var hotels = contentItem.ContentManager
.Query(VersionOptions.Published)
.Join<CommonPartRecord>().Where(x => x.Container.Id == container.Id)
.Join<ContainablePartRecord>().OrderByDescending(x => x.Position);
foreach(var hotel in hotels) {
// do something
}
}
}
else {
<span class="value">#T("No content items.")</span>
}
</p>
I'm missing some using statements at the top but the gist of it is there.

Related

Is it possible in Orchard to dynamically change HTML in a shape right before rendering?

This is the markup for Content.ThumbnailSummary.cshtml, a custom DisplayType I use to render ContentItems as clickable thumbnails with their contents absolutely positioned over them.
#using Orchard.Utility.Extensions;
#{
var contentTypeClassName = ((string)Model.ContentItem.ContentType).HtmlClassify();
}
<a class="content-item #contentTypeClassName thumbnail-summary">
#Display(Model.Header)
<div class="thumbnail-summary-inner">
#Display(Model.Content)
</div>
#Display(Model.Footer)
</a>
The problem is that out of the box most Parts and Fields get rendered as links or paragraphs containing links, and nested <a> tags mess up DOM rendering pretty badly in most browsers. A ThumbnailSummary should never contain any links.
I could create alternates for every field and part, or I could remove everything by default in placement and only add rules for specific cases as I need them. But that would be pretty tedious and defeats a lot of the benefits of placement, so I was hoping I could somehow strip or replace all <a> tags in code only for shapes with this DisplayType.
I've been looking in this direction but I'm not sure if it's viable:
public class Shapes : IShapeTableProvider
{
public void Discover(ShapeTableBuilder builder)
{
builder.Describe("Content")
.OnDisplaying(displaying =>
{
if (displaying.ShapeMetadata.DisplayType == "ThumbnailSummary")
{
// Do something here???
}
});
}
}
You are almost right, instead of a provider add a class that inherits from Orchard.DisplayManagement.Implementation.ShapeDisplayEvents or implement IShapeDisplayEvents yourself.
I've done this myself to remove certain functionality from admin area that cannot be disabled via feature or permission.
The code should look like this
public class MyShapeDisplayEvents : Orchard.DisplayManagement.Implementation.ShapeDisplayEvents
{
public override void Displayed(Orchard.DisplayManagement.Implementation.ShapeDisplayedContext context)
{
if (context.Shape is Orchard.DisplayManagement.Shapes.Shape)
{
Orchard.DisplayManagement.Shapes.Shape lShape = (Orchard.DisplayManagement.Shapes.Shape)context.Shape;
if (lShape.Metadata.Type == "Layout")
{
string lChildContent = context.ChildContent.ToHtmlString();
// do something with the content like removing tags
context.ChildContent = new System.Web.HtmlString(lChildContent);
}
...

How do I maintain the correct order of widgets in an overridden Zone template?

I overrode the Zone-AsideSecond template like so to add some classes to the tag:
#{
Model.Id = "zone-aside-second";
var tag = Tag(Model, "section");
tag.Attributes.Add("data-spy", "affix");
tag.Attributes.Add("data-offset-top", "300");
}
#tag.StartElement
#DisplayChildren(Model)
#tag.EndElement
But now the widgets in the zone don't respect their Position anymore and are just rendered in the order they were created. I tried fixing it like this:
Model.Items = ((IEnumerable<dynamic>)Model.Items).OrderBy(c => Int32.Parse(c.ContentItem.WidgetPart.Position));
But that didn't seem to make a difference.
Orchard has an method to order widgets in the right way, you can use it as following:
#foreach (var item in Orchard.Core.Shapes.CoreShapes.Order(Model)) {
#Display(item)
}

Orchard CMS - Extending Users with Fields - exposing values in Blog Post

I'd like to extend the users content definition to include a short bio and picture that can be viewed on every blog post of an existing blog. I'm unsure of what the best method to do this is.
I have tried extending the User content type with those fields, but I can't seem to see them in the Model using the shape tracing tool on the front end.
Is there a way to pass through fields on the User shape in a blog post? If so, what is the best way to do it?
I also have done this a lot, and always include some custom functionality to achieve this.
There is a way to do this OOTB, but it's not the best IMO. You always have the 'Owner' property on the CommonPart of any content item, so in your blogpost view you can do this:
#{
var owner = Model.ContentItem.CommonPart.Owner;
}
<!-- This automatically builds anything that is attached to the user, except for what's in the UserPart (email, username, ..) -->
<h4>#owner.UserName</h4>
#Display(BuildDisplay((IUser) owner))
<!-- Or, with specific properties: -->
<h1>#T("Author:")</h1>
<h4>#owner.UserName</h4>
<label>#T("Biography")</label>
<p>
#Html.Raw(owner.BodyPart.Text)
</p>
<!-- <owner content item>.<Part with the image field>.<Name of the image field>.FirstMediaUrl (assuming you use MediaLibraryPickerField) -->
<img src="#owner.User.Image.FirstMediaUrl" />
What I often do though is creating a custom driver for this, so you can make use of placement.info and follow the orchard's best practices:
CommonPartDriver:
public class CommonPartDriver : ContentPartDriver<CommonPart> {
protected override DriverResult Display(CommonPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_Common_Owner", () => {
if (part.Owner == null)
return null;
var ownerShape = _contentManager.BuildDisplay(part.Owner);
return shapeHelper.Parts_Common_Owner(Owner: part.Owner, OwnerShape: ownerShape);
});
}
}
Views/Parts.Common.Owner.cshtml:
<h1>#T("Author")</h1>
<h3>#Model.Owner.UserName</h3>
#Display(Model.OwnerShape)
Placement.info:
<Placement>
<!-- Place in aside second zone -->
<Place Parts_Common_Owner="/AsideSecond:before" />
</Placement>
IMHO the best way to have a simple extension on an Orchard user, is to create a ContentPart, e.g. "UserExtensions", and attach it to the Orchard user.
This UserExtensions part can then hold your fields, etc.
This way, your extensions are clearly separated from the core user.
To access this part and its fields in the front-end, just add an alternate for the particular view you want to override.
Is there a way to pass through fields on the User shape in a blog post?
Do you want to display a nice picture / vita / whatever of the blog posts author? If so:
This could be your Content-BlogPost.Detail.cshtml - Alternate
#using Orchard.Blogs.Models
#using Orchard.MediaLibrary.Fields
#using Orchard.Users.Models
#using Orchard.Utility.Extensions
#{
// Standard Orchard stuff here...
if ( Model.Title != null )
{
Layout.Title = Model.Title;
}
Model.Classes.Add("content-item");
var contentTypeClassName = ( (string)Model.ContentItem.ContentType ).HtmlClassify();
Model.Classes.Add(contentTypeClassName);
var tag = Tag(Model, "article");
// And here we go:
// Get the blogPost
var blogPostPart = (BlogPostPart)Model.ContentItem.BlogPostPart;
// Either access the creator directly
var blogPostAuthor = blogPostPart.Creator;
// Or go this way
var blogPostAuthorAsUserPart = ( (dynamic)blogPostPart.ContentItem ).UserPart as UserPart;
// Access your UserExtensions part
var userExtensions = ( (dynamic)blogPostAuthor.ContentItem ).UserExtensions;
// profit
var profilePicture = (MediaLibraryPickerField)userExtensions.ProfilePicture;
}
#tag.StartElement
<header>
#Display(Model.Header)
#if ( Model.Meta != null )
{
<div class="metadata">
#Display(Model.Meta)
</div>
}
<div class="author">
<img src="#profilePicture.FirstMediaUrl"/>
</div>
</header>
#Display(Model.Content)
#if ( Model.Footer != null )
{
<footer>
#Display(Model.Footer)
</footer>
}
#tag.EndElement
Hope this helps, here's the proof:

how to add object to a list in a view?

I have a strongly typed view where I have a form. I display values of a Contact (object) in Textboxes. The contact has a list of functions. I have also a list of all the functions that exist in database. In the view I'm listing all the functions by displaying checkboxes (value : Id of the function, display : name of the function). Before that, I compare the list of the Contact's functions to all to functions and I checked those of the contact. Like that :
#foreach (extranetClient.Models.Classes.FonctionContact fonction in ViewBag.Fonctions)
{
string coche = "";
if ((#Model.ListeFonctions).Where(c => c.IdFonction == fonction.IdFonction).Count() > 0)
{
coche = "checked";
}
<input type="checkbox" #coche id="#fonction.IdFonction" />#fonction.LibelleFonction <br />
}
It looks like that:
But now, if the user checks a checkbox to add a function to the contact, I need to save it in the contact's list. I cannot find how to do that. Somebody has idea ?
First we need to modify your checkboxes so they will post properly. When a checkbox is checked it will be posted like this name=value. But we want all your checkboxes to be posted into a collection, so the name will follow this format function[x] where x is an index of the checkbox. The value then becomes the ID of the function.
#{
var i = 0;
}
#foreach (extranetClient.Models.Classes.FonctionContact fonction in ViewBag.Fonctions)
{
string coche = "";
if ((#Model.ListeFonctions).Any(c => c.IdFonction == fonction.IdFonction))
{
coche = "checked";
}
<input type="checkbox" #coche id="functions[#(i++)]" value="#fonction.IdFonction" />#fonction.LibelleFonction <br />
}
Now create an action method which can receive the checkboxes's data. Simply provide a parameter which can recieve an enumeration of int assuming your function IDs are ints.
public ActionResult UpdateFunctions(IEnumerable<int> functions)
{
// TODO: functions contains IDs of all checked boxes.
}
Hope this helps.

Orchard - How to display custom Term fields in a taxonomy field?

I've got a contentType (product) that has a taxonomy field (features). The taxonomy term (product feature term) has been customized to include an image field and a description field.
I'd like for the product detail view to display the image from the term along with the name, but I can't find the property to access it.
I've created the following:
Taxonomy
ProductFeature Taxonomony
Vocabulary: Feat1, Feat2, Feat3
ContentTypes
Product
Fields: Features(Taxonomy)
Product Features Term
Fields: Description(Html), Image(Image)
Views
Fields.Contrib.TaxonomyField-Features.cshtml
<!-- Old Code -->
#if (Model.Terms.Count > 0) {
<p class="taxonomy-field">
<span class="name">#name.CamelFriendly():</span>
#(new HtmlString( string.Join(", ", terms.Select(t => Html.ItemDisplayLink(Html.Encode(t.Name), t.ContentItem ).ToString()).ToArray()) ))
</p>
}
<!-- New Code -->
#if (Model.Terms.Count > 0)
{
<div>
#foreach (var myTerm in Model.Terms)
{
#Display(???)
}
</div>
}
What do replace the question marks with? I'd thought it'd be myTerm.Image but that field doesn't exist on the dynamic object.
I've attached an image of the designer viewer.
If you wanted to use the current dev branch on the module, you could access the TermsPart of the content items, which leads you to all currently applied terms.
If you are using version 0.9 of the module, then you can dynamically have access to the fields by getting a reference to your Content Item, then do contentItem.PARTNAME.FIELDNAME. In the case of a type named Product, and a field name Feature it would be contentItem.Product.Feature. Then if this term has a property named Image, it will be termContentItem.ProductTerm.Image.
I would need more information to give you the exact syntax, like the type of field, exact name of content types. Or you can post the question on the module's codeplex project discussion forum.
As Sebastien helped me figure out over on http://orchardtaxonomies.codeplex.com/discussions/263844
Below is what ended up working.
(The key bit being: contentField = myTerm.ContentItem.Features.TermImage;)
#foreach (var myTerm in Model.Terms)
{
var contentField = myTerm.ContentItem.Features.TermImage;
if (!String.IsNullOrWhiteSpace(contentField.FileName)) {
<p class="image-field">
<img src="#Url.Content(contentField.FileName)" alt="#contentField.AlternateText" width="#contentField.Width" height="#contentField.Height"/>
</p>
}
}

Resources