七、制作主题(四) Accessing and rendering shapes
shape是一个动态数据模型。shape的目的使用动态shape能在运行时使用一个model更新并替换ASP.NET MVC的静态view model,你可以认为shape是在模板中显示一大块数据。
本文介绍了shape的概念及解释如何使用。
Introducing Shapes
shape是一个动态数据模型,使用shape 模板为用户制造可见数据。shape模板是为呈现shapes的标记的片段。例如shapes包含menu.menu items,content items,documents,messages.
一个shape是从Orchard.DisplayManagement.Shapes.Shape类继承的数据模型对象。shape类不能实例化。shape是在运行时通过shape factory创建的,默认的shape factory是Orchard.DisplayManagement.Implementation.DefaultShapeFactory,shape factory创建的shape是动态的对象。
备注:动态对象是.net 4的新内容,一个动态对象在运行时而不是编译时暴露它的成员,对比一下,一个ASP.NET MVC model object是一个在编译时定义的静态对象。
关于shape的信息包含在这个shape的ShapeMetadata属性中,这个信息中包含了the shape's type, display type, position, prefix, wrappers, alternates, child content, and a
WasExecuted
Boolean value.
You can access the shape's metadata as shown in the following example:
var shapeType = shapeName.Metadata.Type;
shape对象创建后,使用shape模板呈现这个shape,shape是一段html标记,另一种方法是使用一个shape特性(Orchard.DisplayManagement.ShapeAttribute
),使你写代码创建和显示该shape,不使用模板。
Creating Shapes
对于模块开发者,最常见的需求是从driver传输数据到一个要呈现的模板。drvier从Orchard.ContentManagement.Drivers.ContentPartDriver类继承,通常重写Display 和 Editor方法。Display 和 Editor方法返回一个ContentShapeResult对象,与ASP.NET MVC中的action返回的ActionResult对象相似。ContentShape方法帮助你创建shape并在
ContentShapeResult对象中返回。
尽管
ContentShape方法是重载方法,最典型的使用是传给它两个参数-shape类开和一个定义该shape的动态函数表达式。shape类型命名该shape并绑定该shape到会呈现它的模板。
函数表达式最好用一个示例描述。下面的示例展示了driver’s Display方法返回一个shape result,用来显示Map part:
protected override DriverResult Display( MapPart part, string displayType, dynamic shapeHelper) { return ContentShape("Parts_Map", () => shapeHelper.Parts_Map( Longitude: part.Longitude, Latitude: part.Latitude)); }
表达式使用一个动态对象(shapeHelper)定义一个Parts_Map shape和它的特性。表达式添加一个Longitude属性到shape并设置它等于该part’s
Longitude属性。表达式也为shape添加了一个
Latitudent属性并设置它等于part’s
Latitude属性,
ContentShape方法创建Display方法返回的results对象。
下面示例展示entire driver类,发送一个shape result给一个模板也在Map part中显示或编辑,Display方法用来显示该map,Editor方法为用户输入 使用”GET”方式在编辑模式显示shape result,Editor方法使用”POST”模式用用户输入的值重新显示该 editor view,这些方法使用不同Editor方法的重载:
using Maps.Models; using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; namespace Maps.Drivers { public class MapPartDriver : ContentPartDriver<MapPart> { protected override DriverResult Display( MapPart part, string displayType, dynamic shapeHelper) { return ContentShape("Parts_Map", () => shapeHelper.Parts_Map( Longitude: part.Longitude, Latitude: part.Latitude)); } //GET protected override DriverResult Editor( MapPart part, dynamic shapeHelper) { return ContentShape("Parts_Map_Edit", () => shapeHelper.EditorTemplate( TemplateName: "Parts/Map", Model: part)); } //POST protected override DriverResult Editor( MapPart part, IUpdateModel updater, dynamic shapeHelper) { updater.TryUpdateModel(part, Prefix, null, null); return Editor(part, shapeHelper); } } }
Editor方法通过“GET”使用ContentShape方法为editor模板创建一个shape,这种情况下,type name是Parts_Map_Edit和
shapeHelper对象创建一个EditorTemplate shape,这几个shape有
TemplateName和Model属性,
TemplateName属性带部分路径到模板,这种情况下,
"Parts/Map" 因此Orchard会在你的模块的下面路径下寻找:Views/EditorTemplates/Parts/Map.cshtml
属性带Part’s model文件名,不用带文件扩展名。Model
Naming Shapes and Templates
如前所述,shape type的名称用来绑定shape到模板,并用来渲染shape,例如:假设你创建了一个名叫Map 的part,为指定的longitude and latitude显示map,shape type的名称可能是 Parts_map。根据这个规则,所有的part shapes以Parts_开始,绩效考核part的名称。这个名称(Parts_Map),Orchard在你模块的下面位置寻找:views/parts/Map.cshtml,下表描述命名shape types和templates的规则:
Applied To | Shape Naming Convention | Shape Type Example | Template Example |
Content shapes | Content__[ContentType] | Content__BlogPost | Content-BlogPost |
Content shapes | Content__[Id] | Content__42 | Content-42 |
Content shapes | Content__[DisplayType] | Content__Summary | Content.Summary |
Content shapes | Content_[DisplayType]__[ContentType] | Content_Summary__BlogPost | Content-BlogPost.Summary |
Content shapes | Content_[DisplayType]__[Id] | Content_Summary__42 | Content-42.Summary |
Content.Edit shapes | Content_Edit__[DisplayType] | Content_Edit__Page | Content-Page.Edit |
Content Part templates | [ShapeType]__[Id] | Parts_Common_Metadata__42 | Parts/Common.Metadata-42 |
Content Part templates | [ShapeType]__[ContentType] | Parts_Common_Metadata__BlogPost | Parts/Common.Metadata-BlogPost |
Field templates | [ShapeType]__[FieldName] | Fields_Common_Text__Teaser | Fields/Common.Text-Teaser |
Field templates | [ShapeType]__[PartName] | Fields_Common_Text__TeaserPart | Fileds/Common.Text-TeaserPart |
Field templates | [ShapeType]__[ContentType]__[PartName] | Fields_Common_Text__Blog__TeaserPart | Fields/Common.Text-Blog-TeaserPart |
Field templates | [ShapeType]__[PartName]__[FieldName] | Fields_Common_Text__TeaserPart__Teaser | Fields/Common.Text-TeaserPart-Teaser |
Field templates | [ShapeType]__[ContentType]__[FieldName] | Fields_Common_Text__Blog__Teaser | Fields/Common.Text-Blog-Teaser |
Field templates | [ShapeType]__[ContentType]__[PartName]__[FieldName] | Fields_Common_Text__Blog__TeaserPart__Teaser | Fields/Common.Text-Blog-TeaserPart-Teaser |
LocalMenu | LocalMenu__[MenuName] | LocalMenu__main | LocalMenu-main |
LocalMenuItem | LocalMenuItem__[MenuName] | LocalMenuItem__main | LocalMenuItem-main |
Menu | Menu__[MenuName] | Menu__main | Menu-main |
MenuItem | MenuItem__[MenuName] | MenuItem__main | MenuItem-main |
Resource | Resource__[FileName] | Resource__flower.gif | Resource-flower.gif |
Style | Style__[FileName] | Style__site.css | Style-site.css |
Widget | Widget__[ContentType] | Widget__HtmlWidget | Widget-HtmlWidget |
Widget | Widget__[ZoneName] | Widget__AsideSecond | Widget-AsideSecond |
Zone | Zone__[ZoneName] | Zone__AsideSecond | Zone-AsideSecond |
你应该以下面的规则放置你项目中的模板:
- Content item shape templates are in the views/itemsfolder.
Parts_
shape templates are in the views/partsfolder.Fields_
shape templates are in the views/fieldsfolder.- The
EditorTemplate
shape templates are in the views/EditorTemplates/template
name folder.
For example, anEditorTemplate
with a template name of Parts/Routable.RoutePart has its template at views/EditorTemplates/Parts/Routable.RoutePart.cshtml. - All other shape templates are in the views folder.
备注:模板扩展名能是激活的视图引擎支持的任何扩展名,比如:.cshtml,.vbhtml , .ascx
From Template File Name to Shape Name
通常地,从一个模板文件名映射到对应的shape名称的规则如下:
- 点(.) 和反斜线(\) 变为下划线(_)。 备注:这不意味着Views的子目录myviews 下的_example.cshtml 文件与Views下的myviewsexample.chtml_文件一样, shape模板仍旧在预期的目录(见上文)。
- 连接符 (-) 变为双下划线(__).
-
例如:Views/Hello.World.cshtml会用于呈现一个名称为Hello_Worldr的shape,Views/Hello.World-85.cshtml会用于呈现名称为
Hello_World__85
的shape。
Alternate Shape Rendering
如前文所述,
AsideSecond zone中的一个HTML widget应该被widget.cshtml模板、widget-htmlwidget.cshtml模板或widget-asidesecond.cshtml呈现(如果存在的话),当一些呈现相同内容的可能性存在,这些引用到候选shape,并且他们丰富了模板。一组候选对应相同的shape,它们之间的不同仅是双下划线后缀。例如:
Hello_World
,Hello_World__85
, andHello_World__DarkBlue是一组Hello_World shape的候选,Hello_World_Summary,相反地,不属于这组,与它相对应的是Hello_World_Shape shape,不是
Hello_World
shape。(注意__和_的不同)Which Alternate Will Be Rendered?
即便有候选,shape也总是以基本的名称创建,就像Hello_World。候选给了主题开发者额外的模板名称选项超出了默认的(比如hello.world.cshtml)。系统会选择从他们之中选择最匹配的模板,因此 hello.world-orange.cshtml会指向hello.world.cshtml(如果存在)。
Built-In Content Item Alternates
上面的表为content items展示了可能的模板名称,现在应该很明确从内容和显示的类型创建shape名称。(例如:Content_Summary)。系统也自动添加内容类型和内容ID 给 候选(例如:Content_Summary__page 和 Content_Summary__42)。
Rendering Shapes Using Templates
shape模板是用来呈现shape的一段标记,Orchard的默认视图引擎是Razor,因此,shape模板使用Razor语法。下面示例展示了显示一个Map part为图片的模板:
<img alt="Location" border="1" src="http://maps.google.com/maps/api/staticmap? &zoom=14 &size=256x256 &maptype=satellite&markers=color:blue|@Model.Latitude,@Model.Longitude &sensor=false" />
这个示例展示一个img元素,src属性包含了一个URL和传递查询字符串值的一组参数,这个查询字符串中,@Model代表被传递到模板的shape,因此,@Model.Latitude是shape的
Latitude属性,
@Model.Longitude是shape的
Longitude属性。
下面示例展示了editor的模板,这个模板使用户输入latitude and longitude值:
@model Maps.Models.MapPart <fieldset> <legend>Map Fields</legend> <div class="editor-label"> @Html.LabelFor(model => model.Longitude) </div> <div class="editor-field"> @Html.TextBoxFor(model => model.Latitude) @Html.ValidationMessageFor(model => model.Latitude) </div> <div class="editor-label"> @Html.LabelFor(model => model.Longitude) </div> <div class="editor-field"> @Html.TextBoxFor(model => model.Longitude) @Html.ValidationMessageFor(model => model.Longitude) </div> </fieldset>
@Html.LabelFor表达式使用shape属性名称创建labels,
@Html.TextBoxFor表达式创建关于用户输入shape值的文本框,@Html.ValidationMessageFor表达式显示消息(当用户输入错误时)。
Wrappers
Wrappers让在shape周围添加标记来定制shape的呈现,例如:Document.cshtml 是
Layout
shaper wrapper,因为它在Layout shape周围给了特殊的标记代码。通常,在主题的Views文件夹中添加Wrapper文件,例如:要为widget添加wrapper,就在主题的Views文件夹下添加Widget.Wrapper.cshtml文件。如果你启用了Shape Tracing功能,你将会看到一个shape可用的wrapper名称,也能在placement.info中指定一个wrapper。
Creating a Shape Method
创建和呈现shape的另一种方法是创建一个方法定义和呈现shape,方法必须用Shape特性标记(Orchard.DisplayManagement.ShapeAttribute类),方法返回IHtmlString对象来使用模板,返回对象包含呈现shape的标签。
下面示例展示
DateTimeRelative shape,这个shape传递一个 DateTime值,返回相对应的字符串。
public class DateTimeShapes : IDependency { private readonly IClock _clock; public DateTimeShapes(IClock clock) { _clock = clock; T = NullLocalizer.Instance; } public Localizer T { get; set; } [Shape] public IHtmlString DateTimeRelative(HtmlHelper Html, DateTime dateTimeUtc) { var time = _clock.UtcNow - dateTimeUtc; if (time.TotalDays > 7) return Html.DateTime(dateTimeUtc, T("'on' MMM d yyyy 'at' h:mm tt")); if (time.TotalHours > 24) return T.Plural("1 day ago", "{0} days ago", time.Days); if (time.TotalMinutes > 60) return T.Plural("1 hour ago", "{0} hours ago", time.Hours); if (time.TotalSeconds > 60) return T.Plural("1 minute ago", "{0} minutes ago", time.Minutes); if (time.TotalSeconds > 10) return T.Plural("1 second ago", "{0} seconds ago", time.Seconds); return T("a moment ago"); } }