From f7704ba68fcaff0e076191676ba21f20b1d06b3b Mon Sep 17 00:00:00 2001 From: dougbu Date: Sun, 20 Apr 2014 00:40:12 -0700 Subject: [PATCH] Implement `Editor*()` HTML helpers - copy over legacy `Editor*()` extensions and default editor templates - get working in the new world - usual `var`, `String` -> `string`, `internal` -> `public`, namespaces, ... - longest overloads into `IHtmlHelper[]` and implementation classes - clean up `ViewContext.ViewData` -> `ViewData`, trailing whitespace, long lines - remove `MultilineTextTemplate()` since `TextArea()` doesn't exist yet - remove `ColorInputTemplate()` since `Color` type doesn't exist - use `html.Label()`, not `LabelExtensions.LabelHelper()`: equivalent helper is protected. only downside is potential `ModelMetadata` re-discovery. - rename `HtmlInputTemplateHelper()` -> `GenerateTextBox()` - copy over `Html5DateRenderingMode` and `html.Html5DateRenderingMode` property - and get them working in new world - hook the default editor templates up - use `Editor()`, `EditorFor()`, `EditorForModel()` in MVC sample - add an on-disk editor template to MVC sample --- samples/MvcSample.Web/MvcSample.Web.kproj | 1 + .../Shared/EditorTemplates/Decimal.cshtml | 18 + .../MvcSample.Web/Views/Shared/MyView.cshtml | 71 +++- .../Microsoft.AspNet.Mvc.Core.kproj | 3 + .../Rendering/Html/DefaultEditorTemplates.cs | 363 ++++++++++++++++++ .../Rendering/Html/HtmlHelper.cs | 34 ++ .../Rendering/Html/HtmlHelperOfT.cs | 16 + .../Rendering/Html/TemplateRenderer.cs | 38 +- .../Rendering/Html5DateRenderingMode.cs | 21 + .../Rendering/HtmlHelperEditorExtensions.cs | 105 +++++ .../Rendering/IHtmlHelper.cs | 26 ++ .../Rendering/IHtmlHelperOfT.cs | 23 ++ 12 files changed, 696 insertions(+), 23 deletions(-) create mode 100644 samples/MvcSample.Web/Views/Shared/EditorTemplates/Decimal.cshtml create mode 100644 src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Rendering/Html5DateRenderingMode.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Rendering/HtmlHelperEditorExtensions.cs diff --git a/samples/MvcSample.Web/MvcSample.Web.kproj b/samples/MvcSample.Web/MvcSample.Web.kproj index 018b593fad..99aa4a241b 100644 --- a/samples/MvcSample.Web/MvcSample.Web.kproj +++ b/samples/MvcSample.Web/MvcSample.Web.kproj @@ -29,6 +29,7 @@ + diff --git a/samples/MvcSample.Web/Views/Shared/EditorTemplates/Decimal.cshtml b/samples/MvcSample.Web/Views/Shared/EditorTemplates/Decimal.cshtml new file mode 100644 index 0000000000..5b580266a2 --- /dev/null +++ b/samples/MvcSample.Web/Views/Shared/EditorTemplates/Decimal.cshtml @@ -0,0 +1,18 @@ +@* Override default Decimal template to display value in bold and ignore Html attributes in ViewData. *@ +@using System.Globalization + +@functions { + private object FormattedValue { + get { + if (ViewData.TemplateInfo.FormattedModelValue == ViewData.ModelMetadata.Model) { + return string.Format(CultureInfo.CurrentCulture, "{0:0.00}", ViewData.ModelMetadata.Model); + } + return ViewData.TemplateInfo.FormattedModelValue; + } + } +} + +@Html.TextBox( + name: string.Empty, + value: FormattedValue, + htmlAttributes: new { @class = "text-box single-line", style = "font-weight: bold", }) \ No newline at end of file diff --git a/samples/MvcSample.Web/Views/Shared/MyView.cshtml b/samples/MvcSample.Web/Views/Shared/MyView.cshtml index 1cde356de0..6f4ae4fb6e 100644 --- a/samples/MvcSample.Web/Views/Shared/MyView.cshtml +++ b/samples/MvcSample.Web/Views/Shared/MyView.cshtml @@ -270,111 +270,156 @@ @{ Html.EndForm(); }
+ @using (Html.BeginForm(controllerName: "Home", actionName: "Edit", method: FormMethod.Post)) + { + + + + + +
- @Html.Label("Name", "Display Name") + @Html.Label("Name", "Display / Edit Name") '@Html.Display("Name")' + @Html.Editor("Name") +
- @Html.Label("Dependent.Name", "Display Dependent.Name") + @Html.Label("Dependent.Name", "Display / Edit Dependent.Name") '@Html.Display("Dependent.Name")' + @Html.Editor("Dependent.Name") +
- @Html.Label("Alive", "Display Alive") + @Html.Label("Alive", "Display / Edit Alive") '@Html.Display("Alive")' + @Html.Editor("Alive") +
- @Html.Label("Dependent.Alive", "Display Dependent.Alive") + @Html.Label("Dependent.Alive", "Display / Edit Dependent.Alive") '@Html.Display("Dependent.Alive")' + @Html.Editor("Dependent.Alive") +
- @Html.Label("Age", "Display Age") + @Html.Label("Age", "Display / Edit Age") '@Html.Display("Age")' + @Html.Editor("Age") +
- @Html.Label("GPA", "Display GPA") + @Html.Label("GPA", "Display / Edit GPA") '@Html.Display("GPA")' + @Html.Editor("GPA") +
+ }
+ @using (Html.BeginForm(controllerName: "Home", actionName: "Edit", method: FormMethod.Post)) + { + + + + + +
- @Html.LabelFor(model => model.Name, "DisplayFor Name") + @Html.LabelFor(model => model.Name, "DisplayFor / EditFor Name") '@Html.DisplayFor(model => model.Name)' + @Html.EditorFor(model => model.Name) +
- @Html.LabelFor(model => model.Dependent.Name, "DisplayFor Dependent.Name") + @Html.LabelFor(model => model.Dependent.Name, "DisplayFor / EditFor Dependent.Name") '@Html.DisplayFor(model => model.Dependent.Name)' + @Html.EditorFor(model => model.Dependent.Name) +
- @Html.LabelFor(model => model.Alive, "DisplayFor Alive") + @Html.LabelFor(model => model.Alive, "DisplayFor / EditFor Alive") '@Html.DisplayFor(model => model.Alive)' + @Html.EditorFor(model => model.Alive) +
- @Html.LabelFor(model => model.Dependent.Alive, "DisplayFor Dependent.Alive") + @Html.LabelFor(model => model.Dependent.Alive, "DisplayFor / EditFor Dependent.Alive") '@Html.DisplayFor(model => model.Dependent.Alive)' + @Html.EditorFor(model => model.Dependent.Alive) +
- @Html.LabelFor(model => model.Age, "DisplayFor Age") + @Html.LabelFor(model => model.Age, "DisplayFor / EditFor Age") '@Html.DisplayFor(model => model.Age)' + @Html.EditorFor(model => model.Age) +
- @Html.LabelFor(model => model.GPA, "DisplayFor GPA") + @Html.LabelFor(model => model.GPA, "DisplayFor / EditFor GPA") '@Html.DisplayFor(model => model.GPA)' + @Html.EditorFor(model => model.GPA) +
+ }
-
+
@Html.DisplayForModel()
+
+ @Html.EditorForModel() +
diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj index 319d5a4edd..776463d6f8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj +++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj @@ -159,8 +159,10 @@ + + @@ -173,6 +175,7 @@ + diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs new file mode 100644 index 0000000000..086f517260 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs @@ -0,0 +1,363 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Microsoft.AspNet.DependencyInjection; +using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace Microsoft.AspNet.Mvc.Rendering +{ + public static class DefaultEditorTemplates + { + private const string HtmlAttributeKey = "htmlAttributes"; + + public static string BooleanTemplate(IHtmlHelper html) + { + bool? value = null; + if (html.ViewData.Model != null) + { + value = Convert.ToBoolean(html.ViewData.Model, CultureInfo.InvariantCulture); + } + + return html.ViewData.ModelMetadata.IsNullableValueType ? + BooleanTemplateDropDownList(html, value) : + BooleanTemplateCheckbox(html, value ?? false); + } + + private static string BooleanTemplateCheckbox(IHtmlHelper html, bool value) + { + return html.CheckBox(string.Empty, value, CreateHtmlAttributes(html, "check-box")).ToString(); + } + + private static string BooleanTemplateDropDownList(IHtmlHelper html, bool? value) + { + return html.DropDownList( + string.Empty, + DefaultDisplayTemplates.TriStateValues(value), + CreateHtmlAttributes(html, "list-box tri-state")) + .ToString(); + } + + public static string CollectionTemplate(IHtmlHelper html) + { + var viewData = html.ViewData; + var model = viewData.ModelMetadata.Model; + if (model == null) + { + return string.Empty; + } + + var collection = model as IEnumerable; + if (collection == null) + { + // Only way we could reach here is if user passed templateName: "Collection" to an Editor() overload. + throw new InvalidOperationException(Resources.FormatTemplates_TypeMustImplementIEnumerable( + "Collection", model.GetType().FullName, typeof(IEnumerable).FullName)); + } + + var typeInCollection = typeof(string); + var genericEnumerableType = collection.GetType().ExtractGenericInterface(typeof(IEnumerable<>)); + if (genericEnumerableType != null) + { + typeInCollection = genericEnumerableType.GetGenericArguments()[0]; + } + + var typeInCollectionIsNullableValueType = typeInCollection.IsNullableValueType(); + var oldPrefix = viewData.TemplateInfo.HtmlFieldPrefix; + + try + { + viewData.TemplateInfo.HtmlFieldPrefix = string.Empty; + + var fieldNameBase = oldPrefix; + var result = new StringBuilder(); + + var serviceProvider = html.ViewContext.HttpContext.RequestServices; + var metadataProvider = serviceProvider.GetService(); + var viewEngine = serviceProvider.GetService(); + + var index = 0; + foreach (var item in collection) + { + var itemType = typeInCollection; + if (item != null && !typeInCollectionIsNullableValueType) + { + itemType = item.GetType(); + } + + var metadata = metadataProvider.GetMetadataForType(() => item, itemType); + var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++); + + var templateBuilder = new TemplateBuilder( + viewEngine, + html.ViewContext, + html.ViewData, + metadata, + htmlFieldName: fieldName, + templateName: null, + readOnly: false, + additionalViewData: null); + + var output = templateBuilder.Build(); + result.Append(output); + } + + return result.ToString(); + } + finally + { + viewData.TemplateInfo.HtmlFieldPrefix = oldPrefix; + } + } + + public static string DecimalTemplate(IHtmlHelper html) + { + if (html.ViewData.TemplateInfo.FormattedModelValue == html.ViewData.ModelMetadata.Model) + { + html.ViewData.TemplateInfo.FormattedModelValue = + string.Format(CultureInfo.CurrentCulture, "{0:0.00}", html.ViewData.ModelMetadata.Model); + } + + return StringTemplate(html); + } + + public static string HiddenInputTemplate(IHtmlHelper html) + { + var viewData = html.ViewData; + + // TODO: add ModelMetadata.HideSurroundingHtml and use here (set result to string.Empty) + var result = DefaultDisplayTemplates.StringTemplate(html); + + var model = viewData.Model; + + // Special-case opaque values and arbitrary binary data. + var modelAsByteArray = model as byte[]; + if (modelAsByteArray != null) + { + model = Convert.ToBase64String(modelAsByteArray); + } + + var htmlAttributesObject = viewData[HtmlAttributeKey]; + var hiddenResult = html.Hidden(string.Empty, model, htmlAttributesObject); + result += hiddenResult.ToString(); + + return result; + } + + private static IDictionary CreateHtmlAttributes( + IHtmlHelper html, + string className, + string inputType = null) + { + var htmlAttributesObject = html.ViewData[HtmlAttributeKey]; + if (htmlAttributesObject != null) + { + return MergeHtmlAttributes(htmlAttributesObject, className, inputType); + } + + var htmlAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "class", className } + }; + + if (inputType != null) + { + htmlAttributes.Add("type", inputType); + } + + return htmlAttributes; + } + + private static IDictionary MergeHtmlAttributes( + object htmlAttributesObject, + string className, + string inputType) + { + var htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributesObject); + + object htmlClassObject; + if (htmlAttributes.TryGetValue("class", out htmlClassObject)) + { + var htmlClassName = htmlClassObject.ToString() + " " + className; + htmlAttributes["class"] = htmlClassName; + } + else + { + htmlAttributes.Add("class", className); + } + + // The input type from the provided htmlAttributes overrides the inputType parameter. + if (inputType != null && !htmlAttributes.ContainsKey("type")) + { + htmlAttributes.Add("type", inputType); + } + + return htmlAttributes; + } + + public static string ObjectTemplate(IHtmlHelper html) + { + var viewData = html.ViewData; + var templateInfo = viewData.TemplateInfo; + var modelMetadata = viewData.ModelMetadata; + var builder = new StringBuilder(); + + if (templateInfo.TemplateDepth > 1) + { + // TODO: add ModelMetadata.SimpleDisplayText and use here (return SimpleDisplayText, not ToString()) + return modelMetadata.Model == null ? modelMetadata.NullDisplayText : modelMetadata.Model.ToString(); + } + + var serviceProvider = html.ViewContext.HttpContext.RequestServices; + var viewEngine = serviceProvider.GetService(); + + foreach (var propertyMetadata in modelMetadata.Properties.Where(pm => ShouldShow(pm, templateInfo))) + { + var divTag = new TagBuilder("div"); + + // TODO: add ModelMetadata.HideSurroundingHtml and use here (skip this block) + { + var label = html.Label(propertyMetadata.PropertyName).ToString(); + if (!string.IsNullOrEmpty(label)) + { + divTag.AddCssClass("editor-label"); + divTag.InnerHtml = label; // already escaped + builder.AppendLine(divTag.ToString(TagRenderMode.Normal)); + + // Reset divTag for reuse. + divTag.Attributes.Clear(); + } + + divTag.AddCssClass("editor-field"); + builder.Append(divTag.ToString(TagRenderMode.StartTag)); + } + + var templateBuilder = new TemplateBuilder( + viewEngine, + html.ViewContext, + html.ViewData, + propertyMetadata, + htmlFieldName: propertyMetadata.PropertyName, + templateName: null, + readOnly: false, + additionalViewData: null); + + builder.Append(templateBuilder.Build()); + + // TODO: add ModelMetadata.HideSurroundingHtml and use here (skip this block) + // TODO: Add IHtmlHelper.ValidationMessage() and call just prior to closing the
tag + { + builder.Append(" "); + builder.AppendLine(divTag.ToString(TagRenderMode.EndTag)); + } + } + + return builder.ToString(); + } + + public static string PasswordTemplate(IHtmlHelper html) + { + return html.Password(string.Empty, + html.ViewData.TemplateInfo.FormattedModelValue, + CreateHtmlAttributes(html, "text-box single-line password")) + .ToString(); + } + + private static bool ShouldShow(ModelMetadata metadata, TemplateInfo templateInfo) + { + // TODO: add ModelMetadata.ShowForEdit and include in this calculation (first) + return + !metadata.IsComplexType + && !templateInfo.Visited(metadata); + } + + public static string StringTemplate(IHtmlHelper html) + { + return GenerateTextBox(html); + } + + public static string PhoneNumberInputTemplate(IHtmlHelper html) + { + return GenerateTextBox(html, inputType: "tel"); + } + + public static string UrlInputTemplate(IHtmlHelper html) + { + return GenerateTextBox(html, inputType: "url"); + } + + public static string EmailAddressInputTemplate(IHtmlHelper html) + { + return GenerateTextBox(html, inputType: "email"); + } + + public static string DateTimeInputTemplate(IHtmlHelper html) + { + ApplyRfc3339DateFormattingIfNeeded(html, "{0:yyyy-MM-ddTHH:mm:ss.fffK}"); + return GenerateTextBox(html, inputType: "datetime"); + } + + public static string DateTimeLocalInputTemplate(IHtmlHelper html) + { + ApplyRfc3339DateFormattingIfNeeded(html, "{0:yyyy-MM-ddTHH:mm:ss.fff}"); + return GenerateTextBox(html, inputType: "datetime-local"); + } + + public static string DateInputTemplate(IHtmlHelper html) + { + ApplyRfc3339DateFormattingIfNeeded(html, "{0:yyyy-MM-dd}"); + return GenerateTextBox(html, inputType: "date"); + } + + public static string TimeInputTemplate(IHtmlHelper html) + { + ApplyRfc3339DateFormattingIfNeeded(html, "{0:HH:mm:ss.fff}"); + return GenerateTextBox(html, inputType: "time"); + } + + public static string NumberInputTemplate(IHtmlHelper html) + { + return GenerateTextBox(html, inputType: "number"); + } + + private static void ApplyRfc3339DateFormattingIfNeeded(IHtmlHelper html, string format) + { + if (html.Html5DateRenderingMode != Html5DateRenderingMode.Rfc3339) + { + return; + } + + var metadata = html.ViewData.ModelMetadata; + var value = metadata.Model; + + // TODO: add ModelMetadata.HasNonDefaultEditFormat and use here (also return if true) + if (html.ViewData.TemplateInfo.FormattedModelValue != value) + { + return; + } + + if (value is DateTime || value is DateTimeOffset) + { + html.ViewData.TemplateInfo.FormattedModelValue = + string.Format(CultureInfo.InvariantCulture, format, value); + } + } + + private static string GenerateTextBox(IHtmlHelper html, string inputType = null) + { + return GenerateTextBox(html, inputType, html.ViewData.TemplateInfo.FormattedModelValue); + } + + private static string GenerateTextBox(IHtmlHelper html, string inputType, object value) + { + return html.TextBox( + name: string.Empty, + value: value, + htmlAttributes: CreateHtmlAttributes(html, className: "text-box single-line", inputType: inputType)) + .ToString(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs index e65afb1a59..4702208de6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs @@ -52,6 +52,9 @@ namespace Microsoft.AspNet.Mvc.Rendering IdAttributeDotReplacement = "_"; } + /// + public Html5DateRenderingMode Html5DateRenderingMode { get; set; } + /// public string IdAttributeDotReplacement { get; set; } @@ -273,6 +276,19 @@ namespace Microsoft.AspNet.Mvc.Rendering htmlAttributes: htmlAttributes); } + /// + public HtmlString Editor(string expression, string templateName, string htmlFieldName, + object additionalViewData) + { + var metadata = ExpressionMetadataProvider.FromStringExpression(expression, ViewData, MetadataProvider); + + return GenerateEditor( + metadata, + htmlFieldName ?? ExpressionHelper.GetExpressionText(expression), + templateName, + additionalViewData); + } + /// public HtmlString Hidden(string name, object value, object htmlAttributes) { @@ -642,6 +658,24 @@ namespace Microsoft.AspNet.Mvc.Rendering htmlAttributes: htmlAttributes); } + protected virtual HtmlString GenerateEditor(ModelMetadata metadata, string htmlFieldName, string templateName, + object additionalViewData) + { + var templateBuilder = new TemplateBuilder( + _viewEngine, + ViewContext, + ViewData, + metadata, + htmlFieldName, + templateName, + readOnly: false, + additionalViewData: additionalViewData); + + var templateResult = templateBuilder.Build(); + + return new HtmlString(templateResult); + } + /// /// Writes an opening
tag to the response. When the user submits the form, /// the request will be processed by an action method. diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs index d8de58c5d2..ce6bf60c40 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelperOfT.cs @@ -109,6 +109,22 @@ namespace Microsoft.AspNet.Mvc.Rendering return GenerateDisplayName(metadata, expressionText); } + /// + public HtmlString EditorFor( + [NotNull] Expression> expression, + string templateName, + string htmlFieldName, + object additionalViewData) + { + var metadata = ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider); + + return GenerateEditor( + metadata, + htmlFieldName ?? ExpressionHelper.GetExpressionText(expression), + templateName, + additionalViewData); + } + /// public HtmlString HiddenFor([NotNull] Expression> expression, object htmlAttributes) diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs index 8e9093e11e..b0b2ca71bf 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs @@ -29,6 +29,33 @@ namespace Microsoft.AspNet.Mvc.Rendering { typeof(object).Name, DefaultDisplayTemplates.ObjectTemplate }, }; + // TODO: Add DefaultEditorTemplates.MultilineTextTemplate and place in this dictionary. + private static readonly Dictionary> _defaultEditorActions = + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "HiddenInput", DefaultEditorTemplates.HiddenInputTemplate }, + { "Password", DefaultEditorTemplates.PasswordTemplate }, + { "Text", DefaultEditorTemplates.StringTemplate }, + { "Collection", DefaultEditorTemplates.CollectionTemplate }, + { "PhoneNumber", DefaultEditorTemplates.PhoneNumberInputTemplate }, + { "Url", DefaultEditorTemplates.UrlInputTemplate }, + { "EmailAddress", DefaultEditorTemplates.EmailAddressInputTemplate }, + { "DateTime", DefaultEditorTemplates.DateTimeInputTemplate }, + { "DateTime-local", DefaultEditorTemplates.DateTimeLocalInputTemplate }, + { "Date", DefaultEditorTemplates.DateInputTemplate }, + { "Time", DefaultEditorTemplates.TimeInputTemplate }, + { typeof(byte).Name, DefaultEditorTemplates.NumberInputTemplate }, + { typeof(sbyte).Name, DefaultEditorTemplates.NumberInputTemplate }, + { typeof(int).Name, DefaultEditorTemplates.NumberInputTemplate }, + { typeof(uint).Name, DefaultEditorTemplates.NumberInputTemplate }, + { typeof(long).Name, DefaultEditorTemplates.NumberInputTemplate }, + { typeof(ulong).Name, DefaultEditorTemplates.NumberInputTemplate }, + { typeof(bool).Name, DefaultEditorTemplates.BooleanTemplate }, + { typeof(decimal).Name, DefaultEditorTemplates.DecimalTemplate }, + { typeof(string).Name, DefaultEditorTemplates.StringTemplate }, + { typeof(object).Name, DefaultEditorTemplates.ObjectTemplate }, + }; + private ViewContext _viewContext; private ViewDataDictionary _viewData; private IViewEngine _viewEngine; @@ -89,16 +116,7 @@ namespace Microsoft.AspNet.Mvc.Rendering private Dictionary> GetDefaultActions() { - if (_readOnly) - { - return _defaultDisplayActions; - } - else - { - // TODO: Support Editor() and its default templates. - // (No resource for this message because this line _must_ be very short-lived.) - throw new NotImplementedException("No default editor templates yet"); - } + return _readOnly ? _defaultDisplayActions : _defaultEditorActions; } private IEnumerable GetViewNames() diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html5DateRenderingMode.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html5DateRenderingMode.cs new file mode 100644 index 0000000000..f941adcea9 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html5DateRenderingMode.cs @@ -0,0 +1,21 @@ + +namespace Microsoft.AspNet.Mvc.Rendering +{ + /// + /// Controls the value-rendering method For HTML5 input elements of types such as date, time, datetime and + /// datetime-local. + /// + public enum Html5DateRenderingMode + { + /// + /// Render date and time values according to the current culture's ToString behavior. + /// + CurrentCulture = 0, + + /// + /// Render date and time values as Rfc3339 compliant strings to support HTML5 date and time types of input + /// elements. + /// + Rfc3339, + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/HtmlHelperEditorExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/HtmlHelperEditorExtensions.cs new file mode 100644 index 0000000000..1bb4552386 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/HtmlHelperEditorExtensions.cs @@ -0,0 +1,105 @@ +using System; +using System.Linq.Expressions; + +namespace Microsoft.AspNet.Mvc.Rendering +{ + public static class EditorExtensions + { + public static HtmlString Editor(this IHtmlHelper html, string expression) + { + return html.Editor(expression, templateName: null, htmlFieldName: null, additionalViewData: null); + } + + public static HtmlString Editor(this IHtmlHelper html, string expression, object additionalViewData) + { + return html.Editor(expression, templateName: null, htmlFieldName: null, + additionalViewData: additionalViewData); + } + + public static HtmlString Editor(this IHtmlHelper html, string expression, string templateName) + { + return html.Editor(expression, templateName, htmlFieldName: null, additionalViewData: null); + } + + public static HtmlString Editor(this IHtmlHelper html, string expression, string templateName, + object additionalViewData) + { + return html.Editor(expression, templateName, htmlFieldName: null, additionalViewData: additionalViewData); + } + + public static HtmlString Editor(this IHtmlHelper html, string expression, string templateName, + string htmlFieldName) + { + return html.Editor(expression, templateName, htmlFieldName, additionalViewData: null); + } + + public static HtmlString EditorFor(this IHtmlHelper html, + Expression> expression) + { + return html.EditorFor(expression, templateName: null, htmlFieldName: null, additionalViewData: null); + } + + public static HtmlString EditorFor(this IHtmlHelper html, + Expression> expression, object additionalViewData) + { + return html.EditorFor(expression, templateName: null, htmlFieldName: null, + additionalViewData: additionalViewData); + } + + public static HtmlString EditorFor(this IHtmlHelper html, + Expression> expression, string templateName) + { + return html.EditorFor(expression, templateName, htmlFieldName: null, additionalViewData: null); + } + + public static HtmlString EditorFor(this IHtmlHelper html, + Expression> expression, string templateName, object additionalViewData) + { + return html.EditorFor(expression, templateName, htmlFieldName: null, + additionalViewData: additionalViewData); + } + + public static HtmlString EditorFor(this IHtmlHelper html, + Expression> expression, string templateName, string htmlFieldName) + { + return html.EditorFor(expression, templateName, htmlFieldName, additionalViewData: null); + } + + public static HtmlString EditorForModel(this IHtmlHelper html) + { + return html.Editor(expression: string.Empty, templateName: null, htmlFieldName: null, + additionalViewData: null); + } + + public static HtmlString EditorForModel(this IHtmlHelper html, object additionalViewData) + { + return html.Editor(expression: string.Empty, templateName: null, htmlFieldName: null, + additionalViewData: additionalViewData); + } + + public static HtmlString EditorForModel(this IHtmlHelper html, string templateName) + { + return html.Editor(expression: string.Empty, templateName: templateName, htmlFieldName: null, + additionalViewData: null); + } + + public static HtmlString EditorForModel(this IHtmlHelper html, string templateName, object additionalViewData) + { + return html.Editor(expression: string.Empty, templateName: templateName, htmlFieldName: null, + additionalViewData: additionalViewData); + } + + public static HtmlString EditorForModel(this IHtmlHelper html, string templateName, string htmlFieldName) + { + return html.Editor(expression: string.Empty, templateName: templateName, htmlFieldName: htmlFieldName, + additionalViewData: null); + } + + public static HtmlString EditorForModel(this IHtmlHelper html, string templateName, string htmlFieldName, + object additionalViewData) + { + return html.Editor(expression: string.Empty, templateName: templateName, htmlFieldName: htmlFieldName, + additionalViewData: additionalViewData); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelper.cs index 65f4dc7ca1..ea18514143 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelper.cs @@ -8,6 +8,13 @@ namespace Microsoft.AspNet.Mvc.Rendering ///
public interface IHtmlHelper { + /// + /// Set this property to to have templated helpers such as + /// and render date and time values as RFC 3339 compliant strings. + /// By default these helpers render dates and times using the current culture. + /// + Html5DateRenderingMode Html5DateRenderingMode { get; set; } + /// /// Gets or sets the character that replaces periods in the ID attribute of an element. /// @@ -172,6 +179,25 @@ namespace Microsoft.AspNet.Mvc.Rendering string optionLabel, object htmlAttributes); + /// + /// Returns an HTML input element for each property in the object that is represented by the expression, using + /// the specified template, HTML field ID, and additional view data. + /// + /// An expression that identifies the object that contains the properties to edit. + /// + /// The name of the template that is used to render the object. + /// + /// A string that is used to disambiguate the names of HTML input elements that are rendered for properties + /// that have the same name. + /// + /// + /// An anonymous object or dictionary that can contain additional view data that will be merged into the + /// instance that is created for the template. + /// + /// The HTML markup for the input elements for each property in the object that is represented by the + /// expression. + HtmlString Editor(string expression, string templateName, string htmlFieldName, object additionalViewData); + /// /// Converts the value of the specified object to an HTML-encoded string. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs index b596fc34c3..f79063c5a8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/IHtmlHelperOfT.cs @@ -89,6 +89,29 @@ namespace Microsoft.AspNet.Mvc.Rendering string optionLabel, object htmlAttributes); + /// + /// Returns an HTML input element for each property in the object that is represented by the specified + /// expression, using the specified template, an HTML field ID, and additional view data. + /// + /// The type of the value. + /// An expression that identifies the object that contains the properties to edit. + /// + /// The name of the template that is used to render the object. + /// + /// A string that is used to disambiguate the names of HTML input elements that are rendered for properties + /// that have the same name. + /// + /// + /// An anonymous object or dictionary that can contain additional view data that will be merged into the + /// instance that is created for the template. + /// + /// The HTML markup for the input elements for each property in the object that is represented by the + /// expression. + HtmlString EditorFor([NotNull] Expression> expression, + string templateName, + string htmlFieldName, + object additionalViewData); + /// /// Render an input element of type "hidden". ///