Request for info on how to style content items

Topics: Customizing Orchard
Jan 11, 2014 at 1:20 PM
Hey everyone,

I've just finished another orchard site and I still have the impression that I did too much manual work.

I want to know how to have full control over the styling of content items.
If someone would take the time to explain me this that would be really great (please don't reference me to a bunch of articles.

Say that using the backend I create a content item "Product"

I add some fields "Name" "Price" "Image"

Now I want to display the image first, and let price float next to Name

How can I access these elements using css, all is generated so I don't have any control over them.

How should I do this?

Borrie
Jan 11, 2014 at 6:01 PM
You can render everything you want, including the fields, using an alternate template for a part or the content item (shape tracing will help deciding), and prevent fields rendering themselves using the placement.info file. This will make you lose the flexibility of adding/removing fields later to the content item but I find it the most stright forward.

Other than this, you can change the ordering of fields using placement.info, and add a wrapper to some container (the content part that holds the fields, or the content itself, to render a div around with a custom css class name, and style the fields as u wish using this class name in css selectors.


Jan 12, 2014 at 8:26 AM
Kassobasi,

Thanks for the answer, I always use alternate shapes but don't know how to use the placement info.

Could you post the code for the placement info file giving this simple "Product" example? This would really help me out a lot...

Borrie
Jan 12, 2014 at 1:46 PM
In case of your Product, to hide fields, you can do something like that:

<Match ContentType="Product">
<Place Fields_Common_Text-Name="-" />
<Place Fields_Input-Price="-" />
<Place Fields_MediaLibraryPicker-Image="-" />
</Match>

Name of the shape (the attribute of Place tag) depends on field type and field name. I assumed the field types here, I guess shape tracing will give you more info about shape names.

To change the ordering of fields, something similar:

<Match ContentType="Product">
<Place Fields_Common_Text-Name="Content:2" />
<Place Fields_Input-Price="Content:3" />
<Place Fields_MediaLibraryPicker-Image="Content:before" />
</Match>

I don't know how to add a wrapper to a content part of a content type created by the admin UI. The fields added to a content type are not directly added to a type, but to a hidden part with the same name as the type. But the wrapper might be unnecessary. The Content template adds a css class to the article tag with the content type name (see Core\Contents\Views\Content.cshtml), so you can use that in you css selectors.

If you want to group your fields together, you use something like this (I guess)

<Match ContentType="Product">
<Place Fields_Common_Text-Name="Footer:2" />
<Place Fields_Input-Price="Footer:3" />
<Place Fields_MediaLibraryPicker-Image="Header:1" />
</Match>

so that they will be placed respectively in the article tag of the content (same Content.cshtml template). You can now move divs and sections around using css.


Jan 13, 2014 at 11:10 AM
Kassobassi,

Thank you so much for the info, I finally get the placement.info

I still have a few questions :) I've tried to add a wrapper to the field Name:

<Place Fields_Common_Text-Name="Content:4;Wrapper=Wrapper_Product_Name" />
I've created a cshtml file in my views folder: Wrapper_Product_Name.cshtml
with the following code:
<div class="Wrapper_Product_Name"> @Model.Html </div> in css:

.Wrapper_Product_Name
{
display:block;
}

The div now get's generated but it's empty... (don't know what goes wrong, i followed the exact steps from the document.

(BTW, this really is a lot of work to add one wrapper around one field....)

The field are displayed when default rendered like this: "Name: "NameValue" so the title of the field is always shown, in case you don't want this, should you create an alternate for every field?

Also for the grouping, say i want a custom div wrapped around name and type, how can I do this?

Borrie
Jan 13, 2014 at 2:44 PM
Wrapper is responsible to render its inner content, with a Display(Model.Child) or Display(Model.Metadata.Children) call. I'm not sure which, I've never used wrappers, but I'm pretty sure it's not Model.Html.

It's quite a lot to do wrappers, I agree. I think wrappers are necessary only in certain situations, like when you want common wrappers around different shapes, or when you don't want to take over inner parts rendering with an alternate because somebody else is responsible for it. In your case an alternate for the field makes more sense to me.

Common text field is rendered by this template (orchard 1.7.2, /Common/Views/Fields.Common.Text.cshtml):

@{
string name = Model.ContentField.DisplayName;
}

@if (HasText(name) && HasText(Model.Value)) {

@name: @Model.Value


}

You see there is no setting to prevent field name rendering. So if you want different behavior, you need an alternate template. It depends if you will create an alternate for *every field* or not depending on your requirements. If you want not to display field name for any text field, just one text field alternate would be OK. If you want it work differently for, say, different content types, then you need different alternates.

If you want a custom div around a subset of the fields, I guess you need to take over Content rendering with a Content-Product.cshtml template. There you can choose to implement
1- your custom divs around Display(Model.Header/Footer etc. calls and use placement.info to put fields to that local zones,
or 2- hide the fields own rendering in placement.info (<Place field_x="-" />) and render the fields as you like with code in Content-Product.cshtml template, accessing the fields with Model.ContentItem.Product.Name, Model.ContentItem.Product.Price


Jan 13, 2014 at 2:52 PM
Yeah,

It's fairly complicated and time consuming to really have full control.

If you're interested this is an example of the code I use to have full controll (it includes rendering images with a backup image) and rendering content picker.

I know this is not the suggested way but I think that this is the only way to have full control
  • I create sites for people who don't have any IT knowledge. I create pages and content items for them and they have very limited access to the backend, which is perfect for them, they can update their own site and not screw it up. This is off course for "static" websites. You can't do it if it's a big cms which has a team adding new pages, items and fields all the time.
I think it would be really handy in Orchard if fields included a css field and you could group together fields and parts into custom div's via the backend.

For now I render everything manually

Borrie
@using Orchard.Utility.Extensions;
@using Orchard.ContentPicker.Fields

@{
    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");
}

@{ string fotourl;
 if (Model.ContentItem.Medewerker.Medewerker_Foto.Url == null)
 {
     fotourl = @Url.Content(Html.ThemePath(WorkContext.CurrentTheme, "/Styles/img/default-person.jpg"));
 }


 else
 {
     fotourl = Href(Model.ContentItem.Medewerker.Medewerker_Foto.Url);

 }
}

@tag.StartElement


<div id="Medewerker_Naam"><h3>@Model.ContentItem.Medewerker.TitlePart.Title</h3></div>
<div id="Medewerker_Body">
    <img id="medewerkersfoto" src="@fotourl" />

    <div id="medewerkerscontentDiv">
        <p>@Model.ContentItem.Medewerker.BodyPart.Text</p>

        @{
            var field = (ContentPickerField)Model.ContentItem.Medewerker.Domeinen;
            string name = field.DisplayName;
            var contentItems = field.ContentItems;
        }
        <p class="content-picker-field content-picker-field-@name.HtmlClassify()">
            <span class="name">Terreinen:</span>
            @if (contentItems.Any())
            {
                foreach (var contentItem in contentItems)
                {
                    <span class="value">@Html.ItemDisplayLink(contentItem)</span>
                    if (contentItem != contentItems.Last())
                    {
                        <span>,</span>
                    }
                }
            }
            else
            {
                <span class="value">@T("Er zijn nog geen terreinen toegevoegd voor deze medewerker")</span>
            }
        </p>

        <p>Mail: @Model.ContentItem.Medewerker.Emailadres.Value * Tel: @Model.ContentItem.Medewerker.Telefoonnr.Value</p>

    </div>


</div>



@tag.EndElement

Jan 14, 2014 at 6:07 PM
Kassobasi,

I have one last question, in the code above there's: <span class="value">@Html.ItemDisplayLink(contentItem) Do you know how I can display the title of that contentitem? Now it displays the link to that contentitem but I just want the title.

Borrie
Jan 15, 2014 at 2:23 PM
@using Orchard.ContentManagement;
@{
IContent contentItem = Model.ContentItem;
}....
@contentItem.As<ITitleAspect>().Title


Jan 15, 2014 at 3:17 PM
Kassobasi,

I've tried it a few ways but it doesn't work:
 @{
            var field = (ContentPickerField)Model.ContentItem.Medewerker.Domeinen;
            string name = field.DisplayName;
            var contentItems = field.ContentItems;
        }
        <p class="content-picker-field content-picker-field-@name.HtmlClassify()">
            <span class="name">Terreinen:</span>
            @if (contentItems.Any())
            {
                foreach (IContent contentItem in contentItems)
                {
                    @*<span class="value">@Html.ItemDisplayLink(contentItem)</span>*@
                    
                <span class="value">@contentItem.As<ITitleAspect>().Title</span>
                    if (contentItem != contentItems.Last())
                    {
                        <span>,</span>
                    }
                }
            }
            else
            { ...
I get Compiler Error Message: CS1502: The best overloaded method match for 'System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)' has some invalid arguments

Borrie
Jan 15, 2014 at 3:34 PM
I don't know why. Try @Html.ItemDisplayText(contentItem) instead.


Marked as answer by borrierulez on 1/15/2014 at 7:46 AM
Jan 15, 2014 at 3:41 PM
That's it! You're the man!