开发字段
你能在下面的地址,找到该例子的代码:
http://orcharddatetimefield.codeplex.com/
我们要创建一个日期和时间选择字段,最终实现下面的效果:
执行下面的命令,创建模块:
codegen module CustomFields /IncludeInSolution:true
更改Module.txt文件:
Name: CustomFields
AntiForgery: enabled
Author: Me
Website: http://orcharddatetimefield.codeplex.com
Version: 0.6.1
OrchardVersion: 0.8.0
Description: A bunch of custom fields for use in your custom content types.
Features:
CustomFields:
Description: Custom fields for Orchard.
Category: Fields
DateTimeField:
Description: A date and time field with a friendly UI.
Category: Fields
Dependencies: CustomFields, Orchard.jQuery, Common, Settings
我们发现,在这个module.txt文件中,我们定义了两个特性,这是因为,在这个模块中,我们提供了2种字段。
在模块的根目录中,我们创建Fields文件夹,在Fields文件夹中,创建DateTimeField.cs文件:
using System;
using System.Globalization;
using Orchard.ContentManagement;
using Orchard.ContentManagement.FieldStorage;
using Orchard.Environment.Extensions;
namespace CustomFields.DateTimeField.Fields {
[OrchardFeature("DateTimeField")]
public class DateTimeField : ContentField {
public DateTime? DateTime {
get {
var value = Storage.Get<string>();
DateTime parsedDateTime;
if (System.DateTime.TryParse(value, CultureInfo.InvariantCulture,
DateTimeStyles.AdjustToUniversal, out parsedDateTime)) {
return parsedDateTime;
}
return null;
}
set {
Storage.Set(value == null ?
String.Empty :
value.Value.ToString(CultureInfo.InvariantCulture));
}
}
}
}
我们定义的字段,继承自ContentField,他为我们提供了一些功能,比如保存字段数据,字段数据最终会保存为字符串类型,实际需要的类型和字符串类型的转换,是自动完成的。
在根目录中,创建ViewModels 文件夹,在ViewModels 文件夹中,创建DateTimeFieldViewModel.cs 文件:
namespace CustomFields.DateTimeField.ViewModels {
public class DateTimeFieldViewModel {
public string Name { get; set; }
public string Date { get; set; }
public string Time { get; set; }
public bool ShowDate { get; set; }
public bool ShowTime { get; set; }
}
}
为了字段能够灵活适应不同的场景,我们需要给字段添加配置,这样,后台管理内容类型的时候,可以对字段进行配置,以便按照我们需要的样子显示。
在根目录下面,创建Settings文件夹,在Settings文件夹中,创建DateTimeFieldSettings.cs 文件:
namespace CustomFields.DateTimeField.Settings {
public enum DateTimeFieldDisplays {
DateAndTime,
DateOnly,
TimeOnly
}
public class DateTimeFieldSettings {
public DateTimeFieldDisplays Display { get; set; }
}
}
我们创建了一个DateTimeFieldSettings类,他只有一个Display属性,是个枚举类型。
和元件一样,一个字段也有一个Driver,用于显示和编辑内容。
在Drivers文件夹下,创建 DateTimeFieldDriver.cs文件:
using System;
using JetBrains.Annotations;
using Orchard;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using CustomFields.DateTimeField.Settings;
using CustomFields.DateTimeField.ViewModels;
using Orchard.ContentManagement.Handlers;
using Orchard.Localization;
namespace CustomFields.DateTimeField.Drivers {
[UsedImplicitly]
public class DateTimeFieldDriver : ContentFieldDriver<Fields.DateTimeField> {
public IOrchardServices Services { get; set; }
// EditorTemplates/Fields/Custom.DateTime.cshtml
private const string TemplateName = "Fields/Custom.DateTime";
public DateTimeFieldDriver(IOrchardServices services) {
Services = services;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
private static string GetPrefix(ContentField field, ContentPart part) {
// handles spaces in field names
return (part.PartDefinition.Name + "." + field.Name)
.Replace(" ", "_");
}
protected override DriverResult Display(
ContentPart part, Fields.DateTimeField field,
string displayType, dynamic shapeHelper) {
var settings = field.PartFieldDefinition.Settings
.GetModel<DateTimeFieldSettings>();
var value = field.DateTime;
return ContentShape("Fields_Custom_DateTime", // key in Shape Table
field.Name, // used to differentiate shapes in placement.info overrides, e.g. Fields_Common_Text-DIFFERENTIATOR
// this is the actual Shape which will be resolved
// (Fields/Custom.DateTime.cshtml)
s =>
s.Name(field.Name)
.Date(value.HasValue ?
value.Value.ToLocalTime().ToShortDateString() :
String.Empty)
.Time(value.HasValue ?
value.Value.ToLocalTime().ToShortTimeString() :
String.Empty)
.ShowDate(
settings.Display == DateTimeFieldDisplays.DateAndTime ||
settings.Display == DateTimeFieldDisplays.DateOnly)
.ShowTime(
settings.Display == DateTimeFieldDisplays.DateAndTime ||
settings.Display == DateTimeFieldDisplays.TimeOnly)
);
}
protected override DriverResult Editor(ContentPart part,
Fields.DateTimeField field,
dynamic shapeHelper) {
var settings = field.PartFieldDefinition.Settings
.GetModel<DateTimeFieldSettings>();
var value = field.DateTime;
if (value.HasValue) {
value = value.Value.ToLocalTime();
}
var viewModel = new DateTimeFieldViewModel {
Name = field.Name,
Date = value.HasValue ?
value.Value.ToLocalTime().ToShortDateString() : "",
Time = value.HasValue ?
value.Value.ToLocalTime().ToShortTimeString() : "",
ShowDate =
settings.Display == DateTimeFieldDisplays.DateAndTime ||
settings.Display == DateTimeFieldDisplays.DateOnly,
ShowTime =
settings.Display == DateTimeFieldDisplays.DateAndTime ||
settings.Display == DateTimeFieldDisplays.TimeOnly
};
return ContentShape("Fields_Custom_DateTime_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: TemplateName,
Model: viewModel,
Prefix: GetPrefix(field, part)));
}
protected override DriverResult Editor(ContentPart part,
Fields.DateTimeField field,
IUpdateModel updater,
dynamic shapeHelper) {
var viewModel = new DateTimeFieldViewModel();
if (updater.TryUpdateModel(viewModel,
GetPrefix(field, part), null, null)) {
DateTime value;
var settings = field.PartFieldDefinition.Settings
.GetModel<DateTimeFieldSettings>();
if (settings.Display == DateTimeFieldDisplays.DateOnly) {
viewModel.Time = DateTime.Now.ToShortTimeString();
}
if (settings.Display == DateTimeFieldDisplays.TimeOnly) {
viewModel.Date = DateTime.Now.ToShortDateString();
}
if (DateTime.TryParse(
viewModel.Date + " " + viewModel.Time, out value)) {
field.DateTime = value.ToUniversalTime();
}
else {
updater.AddModelError(GetPrefix(field, part),
T("{0} is an invalid date and time",
field.Name));
field.DateTime = null;
}
}
return Editor(part, field, shapeHelper);
}
protected override void Importing(ContentPart part, Fields.DateTimeField field,
ImportContentContext context) {
var importedText = context.Attribute(GetPrefix(field, part), "DateTime");
if (importedText != null) {
field.Storage.Set(null, importedText);
}
}
protected override void Exporting(ContentPart part, Fields.DateTimeField field,
ExportContentContext context) {
context.Element(GetPrefix(field, part))
.SetAttributeValue("DateTime", field.Storage.Get<string>(null));
}
}
}
这个Driver继承自ContentFieldDriver<DateTimeField> 。
我们通过依赖注入实例化了T对象,这样,我们通过调用T()方法,可以本地化代码中的字符串。
我们有两个行为, Display 和 Editor,用来创建显示和渲染时候的Shape。
注意: UsedImplicitly 特性只有当你安装了Resharper插件的时候才有用,可以去掉。
shapeHelper 提供了创建Shape的方法。
第二个 Editor 方法会在用户提交编辑页面的时候触发。他将提交的数据保存到字段,然后调用第一个 Editor 返回编辑页面。
我们需要创建模板,来决定如何显示字段。
在Views文件夹中,创建Fields和EditorTemplates/Fields 文件夹,在两个文件夹中都创建Custom.DateTime.cshtml视图。
Fields/Custom.DateTime.cshtml:
<p class="text-field"><span class="name">@Model.Name:</span>
@if(Model.ShowDate) { <text>@Model.Date</text> }
@if(Model.ShowTime) { <text>@Model.Time</text> }
</p>
这个模板显示了字段的名称,根据配置,显示日期或者时间。
Views/EditorTemplates/Fields :
@model CustomFields.DateTimeField.ViewModels.DateTimeFieldViewModel
@{
Style.Include("datetime.css");
Style.Require("jQueryUI_DatePicker");
Style.Require("jQueryUtils_TimePicker");
Style.Require("jQueryUI_Orchard");
Script.Require("jQuery");
Script.Require("jQueryUtils");
Script.Require("jQueryUI_Core");
Script.Require("jQueryUI_Widget");
Script.Require("jQueryUI_DatePicker");
Script.Require("jQueryUtils_TimePicker");
}
<fieldset>
<label for="@Html.FieldIdFor(m => Model.Date)">@Model.Name</label>
@if ( Model.ShowDate ) {
<label class="forpicker"
for="@Html.FieldIdFor(m => Model.Date)">@T("Date")</label>
<span class="date">@Html.EditorFor(m => m.Date)</span>
}
@if ( Model.ShowTime ) {
<label class="forpicker"
for="@Html.FieldIdFor(m => Model.Time)">@T("Time")</label>
<span class="time">@Html.EditorFor(m => m.Time)</span>
}
@if(Model.ShowDate) { <text>@Html.ValidationMessageFor(m=>m.Date)</text> }
@if(Model.ShowTime) { <text>@Html.ValidationMessageFor(m=>m.Time)</text> }
</fieldset>
@using(Script.Foot()) {
<script type="text/javascript">
$(function () {
$("#@Html.FieldIdFor(m => Model.Date)").datepicker();
$("#@Html.FieldIdFor(m => Model.Time)").timepickr();
});
</script>
}
这个模板注册了一些脚本和样式。然后通过脚本,实现了日期时间的编辑。
为了字段的显示,我们需要在根目录创建placement.info文件,决定字段渲染时的位置:
<Placement>
<Place Fields_Custom_DateTime_Edit="Content:2.5"/>
<Place Fields_Custom_DateTime="Content:2.5"/>
</Placement>
在根目录的Settings文件夹中,创建 DateTimeFieldEditorEvents.cs 文件:
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentManagement.ViewModels;
namespace CustomFields.DateTimeField.Settings {
public class DateTimeFieldEditorEvents : ContentDefinitionEditorEventsBase {
public override IEnumerable<TemplateViewModel>
PartFieldEditor(ContentPartFieldDefinition definition) {
if (definition.FieldDefinition.Name == "DateTimeField") {
var model = definition.Settings.GetModel<DateTimeFieldSettings>();
yield return DefinitionTemplate(model);
}
}
public override IEnumerable<TemplateViewModel> PartFieldEditorUpdate(
ContentPartFieldDefinitionBuilder builder, IUpdateModel updateModel) {
var model = new DateTimeFieldSettings();
if (builder.FieldType != "DateTimeField") {
yield break;
}
if (updateModel.TryUpdateModel(
model, "DateTimeFieldSettings", null, null)) {
builder.WithSetting("DateTimeFieldSettings.Display",
model.Display.ToString());
}
yield return DefinitionTemplate(model);
}
}
}
这个和Driver的Editor方法很相似。第一个方法获取配置,渲染页面,第二个方法获取提交的数据,更改配置,然后调用第一个方法。
在Views->DefinitionTemplates文件夹下面,创建 DateTimeFieldSettings.cshtml 文件:
@model CustomFields.DateTimeField.Settings.DateTimeFieldSettings
@using CustomFields.DateTimeField.Settings;
<fieldset>
<label for="@Html.FieldIdFor(m => m.Display)"
class="forcheckbox">@T("Display options")</label>
<select id="@Html.FieldIdFor(m => m.Display)"
name="@Html.FieldNameFor(m => m.Display)">
@Html.SelectOption(DateTimeFieldDisplays.DateAndTime,
Model.Display == DateTimeFieldDisplays.DateAndTime,
T("Date and time").ToString())
@Html.SelectOption(DateTimeFieldDisplays.DateOnly,
Model.Display == DateTimeFieldDisplays.DateOnly,
T("Date only").ToString())
@Html.SelectOption(DateTimeFieldDisplays.TimeOnly,
Model.Display == DateTimeFieldDisplays.TimeOnly,
T("Time only").ToString())
</select>
@Html.ValidationMessageFor(m => m.Display)
</fieldset>
这个模板创建了一个下拉框,用于用户选择显示方式。
在 Styles 文件夹创建一个datetime.css文件:
html.dyn label.forpicker {
display:none;
}
html.dyn input.hinted {
color:#ccc;
font-style:italic;
}
.date input{
width:10em;
}
.time input {
width:6em;
}
首先,我们需要启用特性,然后再后台的内容类型管理页面,我们就可以选中一个内容类型,比如"Page",在Page的编辑页面,我们点击"Fields"旁边的Add,选择我们开发的DateTime字段:
我们需要显示时间和日期,所以,在字段配置的地方,选择"Date and time"。
在创建内容项的地方,我们可以看到字段的效果了:
字段的显示效果如下: