Create or override a list template/Shape ?

Topics: Customizing Orchard, Writing modules
May 15, 2013 at 6:48 PM
Hi All,

Is there any way to override List() template?

following code in my controller spits following output

<li class="first">
<article class="content-item company">
<header>

</header>
<div style="">
<p style="color:red">CompanyID: Acmeinc</p>
<p>Name: Acme inc.</p>
</div>
<div class="published">May 15 2013 12:34 PM</div> </article></li>

What I am trying to do is create a simple <table>

Regards,
Hayri
    [HttpPost]
    public ActionResult Index(PagerParameters pagerParameters)
    {

        Pager pager = new Pager(_orchardServices.WorkContext.CurrentSite,   pagerParameters);

        var companies = _contentManager.Query<CompanyPart, CompanyPartRecord>().Slice(
            pager.GetStartIndex(), pager.PageSize).ToList();

        dynamic pagerShape = _orchardServices.New.Pager(pager).TotalItemCount(_contentManager.Query("Company").Count());

        var list = _orchardServices.New.List();

         list.AddRange(companies.Select(x => _contentManager.BuildDisplay(x)));

         return View((object)new IndexViewModel
         {
             Items = list,
             Pager = pagerShape
         });

    }
May 16, 2013 at 10:59 AM
Yes. The list is a shape and for all shapes you should be able to create an alternate in the theme. A better recommendation is probably after how to do this below.
  1. Create an alternate for the List shape -> Create a new file / use shape tracing to create the alternate ->
    ---> ~/Themes/{mytheme}/Views/List-{contentType}.cshtml (seeing as your don't seem to have the list on a type try just List.cshtml ...)
  2. Add to the view some markup to describe the table and row, im just copying one of my previous examples of overriding the list template
@model dynamic
@{
    List<object> items = Model.Items;
    var itemCount = 0;
}

<div data-items-count="@items.Count">
    @foreach (var item in items)
    { 
        <div data-item-number="@(++itemCount)">
            @Display(item)
        </div>
    }
</div>
Alternatively
I think a new separate shape would be a better fit?
var list = _orchardServices.New.TableList();
list.Items = companies.Select(x => _contentManager.BuildDisplay(x)).ToList();

return View((object)new IndexViewModel
         {
             Items = list,
             Pager = pagerShape
         });
All you need then is to put a cshtml file called TableList.cshtml in the views folder of your module or in the theme and add your html. I would then change the BuildDisplay to a more detailed version as well:
companies.Select(x => _contentManager.BuildDisplay(x, "TableRow")) //creating a alternate of for the Company TableRow display type so it can be displayed in many different ways - Summary/Detail/TableRow etc  
You could also fulfill this with projections & Table layout as well.
May 16, 2013 at 1:56 PM
Hi Matt,

Thank you for replying. I like the second alternative better. Could you give an example about how to display like "Summary/Detail/TableRow" ?

Regards,
Hayri



May 17, 2013 at 1:53 AM
Hi Matt,

BuildDisplay() method wraps my view with "article" and "header" tag. My content type does not contain title tag so these are just empty tags for each content item. Is there any way to override this? Also I appriciate if you can give a sample for "Summary/Detail/TableRow method.

Regards,
Hayri



May 22, 2013 at 11:57 AM
Hayrix wrote:
Hi Matt, BuildDisplay() method wraps my view with "article" and "header" tag. My content type does not contain title tag so these are just empty tags for each content item. Is there any way to override this? Also I appriciate if you can give a sample for "Summary/Detail/TableRow method. Regards, Hayri
Ok if we look at how the content is displayed normally:

Theme - describes where the zones of content can be placed
-- Zone Content - describes where the content type parts can be placed (normally)
----- Content Type template - can have alternates for display type, content type, path
----- * Normally contains placement areas for the parts and fields
---------- Part templates : organized by placement information - can have alternates for content type, path, id and possibly extended for display type
---------- Field templates : organized by placement information - can have alternates for content type, path, id and possibly extended for display type

Its the Content Type alternates which we will be looking to create alternates for.

Back to the controller and action:
var list = _orchardServices.New.TableList();
//TableRow will be the display type. 
//There are already templates for content - detail and content.summary.cshtml now we want a more specialized one 
list.Items = companies.Select(x => _contentManager.BuildDisplay(x, "TableRow")).ToList(); 

return View((object)new IndexViewModel
         {
             Items = list,
             Pager = pagerShape
         });
_
BuildDisplay - the default display type is detail. Without an alternate for the content type it will use the Orchard.Core/Contents/Views/content.cshtml file.
Summary/Detail/TableRow - Summary and Detail are common display types for Orchard, and TableRow will be the additional one we are creating. We dont need to concern ourselves with the other templates._

From our view we will be needing a new shape TableList.cshtml this would look like:
//there wont be any clever way to describes the column names so needs to be done here.
<table>
    <thead>
        <tr>
            <th>field 1</th>
            <th>field 2</th>
            <th>field 3</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in contentItems) {
            @Display(item)
        }
    </tbody>
</table>
The alternate should look like this:
Content.{contentType}.{displayType}
Content.Company.TableRow.cshtml - this item will know about the content item and describe how the content type is placed out.

But we could go two ways here. Navigate the model to find the values for the 'row' ie
<tr>
<td>@Model.ContentItem.TitlePart.Title</td>
<td>@Model.ContentItem.CommonPart.CreatedUtc</td>
<td>@Model.ContentItem.BodyPart.Text</td>
</tr>
Or add more templates for each part / field <- much better alternative if the field requires a more complex template.
In that case the template will describe where the parts and fields go:
<tr>
<td>@Display(Model.FieldOne)</td>
<td>@Display(Model.FieldTwo)</td>
<td>@Display(Model.FieldThree)</td>
</tr>
The fields and parts can have an alternates for the display type as well if they need different ways to show the data

and we can target parts/fields into those columns as follows:
placement.info
<Match ContentType="Company">
    <Match DisplayType="TableRow">
      <Place Parts_Title="FieldOne:0" />
      <Place Parts_Common_Metadata="FieldTwo:0" />
      <Place Parts_Common_Body="FieldThree:0" />
    </Match>
</Match>
In Overview:
The controller actions' view displays this shape:
TableList.cshtml <- describing the table layout
Which in turn for each row (content in the list) renders these shapes:
Content.Company.TableRow.cshtml <- describing how the content item should look at the "TableRow" display type building on the table row templates

Now I would recommend looking at Projections and Queries. Using the html table layout (specified in the query) could be an easier way of going. As you can specify the column names and value lookup for each content item. It really depends on the complexity really.

Hope that helps.
May 29, 2013 at 8:54 PM
Hi Matt,

Thank you for replying. I totally understood the concept but, I am failing to display the data with code below.

Here are my questions:

1. my "Index" action in my controller redirects to "Index" view. With the following code what should i state in my Index view? @(Display( what?)

2. In the following loop how can i introduce the viewmodel? I think something is missing.

    @foreach (var item in contentItems) {
            @Display(item)
        }
3. What is the correct naming convention for the TableList() shape? 
------- a: Content.Company.TableList.cshtlm ?
--------b: TableList.cshtml?

4. What does "groupId" stands for in BuildDisplay(conten, displayType, groupId) ?

I appreciate if you can complete the code

Thank you again.

Regards,
Hayri


May 30, 2013 at 2:46 AM
Right. Have you considered projections and the table layout for your problem? :)

Lets look at an interesting part first. Shapes. We can ask for any shape to be rendered by
  • var shape = _orchardServices.New.{ShapeName}();
    But also from the shape factory IShapeFactory (which is just _orchardServices.New cast as dynamic) like:
  • var shape = ((IShapeFactory)_orchardServices.New).Create("ShapeName");
    Both cases will cause orchard to go looking for the shape {ShapeName}
When @Display(shape) is called it will look for this shape in two ways:
  1. A Method that describes it (declared by an attribute) - the "List" shape is described in code which i think confuses some people.
  2. A chtml file by the same name. (this should be in the views folder rather than a controller action folder) to be located in a module/views folder or {theme}/views folder .
Lets go back to look at this part.
//a shape that describes the table; we need to render this first:
//_orchardServices.New.TableList() is asking for "TableList.cstml" as _orchardServices.New.MyFancyTableList() is asking for "MyFancyTableList.cshtml"
//its quite useful for bringing in ad hoc shapes, templates, predefined content like you would with partial views 
var list = _orchardServices.New.TableList(); 
//lots of shapes:
//we dont get to name the shapes this time just define the display type the content is in.  
//not sure what group id is for :) 
list.Items = companies.Select(x => _contentManager.BuildDisplay(x, "TableRow")).ToList(); 

return View((object)new IndexViewModel
         {
             Items = list
         });
So our execution begins with with the action producing the view Index.cshtml in the action folder:
@model MyNameSpace.IndexViewModel
@{
   var tableShape = Model.Items; //this is out TableList shape
}
<div>@Display(tableShape)</div>
Now it will be after this file: "TableList.cshtml"; The model is dynamic and we've already described a property for the model as Items (the following line):
list.Items = companies.Select(x => _contentManager.BuildDisplay(x, "TableRow")).ToList(); 
with a list of shapes to further render. So lets begin with TableList.cshtml
@model dynamic
@{
   IList<dynamic> contentTypeShapes = Model.Items; //we can access any Property that we set on the Shape. 
}
<table>
    <thead>
        <tr>
            <th>field 1</th>
            <th>field 2</th>
            <th>field 3</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in contentTypeShapes) {
            @Display(item)
        }
    </tbody>
</table>
@Display(item) above is firstly generating a content shape for the content type. The content type shape describes where parts and fields get placed.
Each driver for the content type is crawled for their shape (or combined shapes) to get rendered onto it, and get placed via placements.

Now we need to take a look how this shape is described as we are not controlling the name of the shape that it is rendering.
... x => _contentManager.BuildDisplay(x, "TableRow") ...
Now by convention it will just use "content.cshtml" which will add in lots of html but we will need an alternate so our content items can be outputed as <tr>'s
So the file should look like:
Content.{contentType}.{displayType}
Content.Company.TableRow.cshtml

Now going back to a similar part of my previous post you should be able to continue again.


So now knowing the above we can answer a few questions:
  1. Index needs to display a shape ("tablelist.cshtml") => @Display(Model.Items) or as above as Model.Items isnt very obvious.
  2. TableList.cshtml Model is dynamic. We can access any propery of it via Model.{Property} as I've shown here:
@model dynamic
@{
   IList<dynamic> contentTypeShapes = Model.Items; //Items was set in the controller action.  
}
  1. TableList.cshtml; but remember its this shape which will be rendering more children shapes hopefully like:
    Content.Company.TableRow.cshtml
    Action>Index.cshtml that renders TableList.cshtml which renders many Content.Company.cshtml
  2. Group Id - Im sure I could guess but im still going with not sure :)
May 31, 2013 at 2:33 AM
Hi Matt,

Thank you so much for your help. Unfortunately I am not able to populate TBody. I guess I am doing something wrong.

Here is my code below :

-------------------------------INDEX ACTION

[HttpPost]
public ActionResult Index(PagerParameters pagerParameters)
{
Pager pager = new Pager(_orchardServices.WorkContext.CurrentSite, pagerParameters);
dynamic pagerShape = _orchardServices.New.Pager(pager).TotalItemCount(_contentManager.Query("Company").Count());

var companies = _contentManager.Query<CompanyPart, CompanyPartRecord>().ForType("Company").Slice(
pager.GetStartIndex(), pager.PageSize).ToList();
var tableShape = _orchardServices.New.TableList();
tableShape.Items = companies.Select(x => _contentManager.BuildDisplay(x, "TableRow")).ToList() ;
return View((object)new IndexViewModel
{
Temp = "aaaabbbbcccc",
TableShape = tableShape,
Pager = pagerShape
});
}


-------------------------TableList.cshtml

@model dynamic
@{
IList<dynamic> contentTypeShapes = Model.Items; //we can access any Property that we set on the Shape.

}


@Model.Items.Count

// Always returns zero

@foreach (var item in contentTypeShapes)
{
@Display(item)
}
CompanyId Company Name

--------------------------- Content.Company.TableRow.cshtml


@Model.ContentItem.CompanyPart.CompanyId
@Model.ContentItem.CompanyPart.CompanyName


tableShape.Items = Returns always zero. As far as I debug my index action content manager returns the data, but I am not able to populate TableRow shape.

Thanks again.

Regards,
Hayri



Jun 2, 2013 at 1:26 AM
I cant spot what is wrong with your sample but hopefully this will help - Ive created a demo module

Once enabled find the menu item in the admin:- Breakout.Company (Migrations should have created 12 test items)
It's got a few different ways of rendering and showing the data.

If that works it should only be a matter of tracking down the differences.

Matt