diff --git a/ChameleonForms.AcceptanceTests/PartialForTests.Should_render_correctly_when_used_via_form_or_section_and_when_used_for_top_level_property_or_sub_property.approved.html b/ChameleonForms.AcceptanceTests/PartialForTests.Should_render_correctly_when_used_via_form_or_section_and_when_used_for_top_level_property_or_sub_property.approved.html index 265ceb42..72930851 100644 --- a/ChameleonForms.AcceptanceTests/PartialForTests.Should_render_correctly_when_used_via_form_or_section_and_when_used_for_top_level_property_or_sub_property.approved.html +++ b/ChameleonForms.AcceptanceTests/PartialForTests.Should_render_correctly_when_used_via_form_or_section_and_when_used_for_top_level_property_or_sub_property.approved.html @@ -11,14 +11,18 @@

Partials

@using (var f = Html.BeginChameleonForm()) { @f.Partial("_ParentPartial") + @f.Partial("_BaseParentPartial") @f.PartialFor(m => m.Child, "_ChildPartial") - + @f.PartialFor(m => m.BaseChild, "_BaseChildPartial") + using (var s = f.BeginSection("This is in the parent view")) { @s.FieldFor(m => m.Decimal).Append("in parent view") @s.Partial("_ParentPartial") + @s.Partial("_BaseParentPartial") @s.FieldFor(m => m.ListId).Append("in parent view") @s.PartialFor(m => m.Child, "_ChildPartial") + @s.PartialFor(m => m.BaseChild, "_BaseChildPartial") @s.FieldFor(m => m.SomeCheckbox).Append(" in parent view") } @@ -51,6 +55,8 @@

Partials

@model ChildViewModel +

This should show the ID of the child field including the parent path: @Html.IdFor(m => m.ChildField)

+ @if (this.IsInFormSection()) { @:@this.FormSection().FieldFor(m => m.ChildField).Append("From partial against child model") @@ -64,6 +70,40 @@

Partials

===== +_BaseParentPartial.cshtml + +@model ViewModelExampleBase + +@if (this.IsInFormSection()) +{ + @:@this.FormSection().FieldFor(m => m.BaseField).Append("from partial against top-level model casted to base class") +} +else +{ + using (var s = this.Form().BeginSection("This is from a form-level partial against the top-level model casted to base class")) { + @s.FieldFor(m => m.BaseField2) + } +} + +===== + +_BaseChildPartial.cshtml + +@model ViewModelExampleBaseChild + +@if (this.IsInFormSection()) +{ + @:@this.FormSection().FieldFor(m => m.BaseChildField).Append("From partial against child model in the base class") +} +else +{ + using (var s = this.Form().BeginSection("This is from a form-level partial against a child model in the base class")) { + @s.FieldFor(m => m.BaseChildField2) + } +} + +===== + Rendered Source
@@ -75,6 +115,17 @@

Partials

+
+ This is from a form-level partial against the top-level model casted to base class +
+
+
+ +
+
+
+

This should show the ID of the child field including the parent path: Child_ChildField

+
This is from a form-level partial against a child model
@@ -87,6 +138,15 @@

Partials

+
+ This is from a form-level partial against a child model in the base class +
+
+
+ +
+
+
This is in the parent view
@@ -99,17 +159,29 @@

Partials

from partial against top-level model +
+
+ from partial against top-level model casted to base class +
+
*
in parent view
+

This should show the ID of the child field including the parent path: Child_ChildField

+
*
From partial against child model
+
+
+ From partial against child model in the base class +
+
*
in parent view diff --git a/ChameleonForms.AcceptanceTests/PartialForTests.cs b/ChameleonForms.AcceptanceTests/PartialForTests.cs index 97f9adff..969fa0f6 100644 --- a/ChameleonForms.AcceptanceTests/PartialForTests.cs +++ b/ChameleonForms.AcceptanceTests/PartialForTests.cs @@ -1,7 +1,5 @@ using System; -using System.CodeDom; using System.IO; -using System.Net.Http; using System.Text.RegularExpressions; using ApprovalTests.Html; using ApprovalTests.Reporters; @@ -21,10 +19,12 @@ public class PartialForTests public void Should_render_correctly_when_used_via_form_or_section_and_when_used_for_top_level_property_or_sub_property() { var renderedSource = GetRenederedSource("/ExampleForms/Partials"); - HtmlApprovals.VerifyHtml(string.Format("Partials.cshtml\r\n\r\n{0}\r\n=====\r\n\r\n_ParentPartial.cshtml\r\n\r\n{1}\r\n=====\r\n\r\n_ChildPartial.cshtml\r\n\r\n{2}\r\n=====\r\n\r\nRendered Source\r\n\r\n{3}", + HtmlApprovals.VerifyHtml(string.Format("Partials.cshtml\r\n\r\n{0}\r\n=====\r\n\r\n_ParentPartial.cshtml\r\n\r\n{1}\r\n=====\r\n\r\n_ChildPartial.cshtml\r\n\r\n{2}\r\n=====\r\n\r\n_BaseParentPartial.cshtml\r\n\r\n{3}\r\n=====\r\n\r\n_BaseChildPartial.cshtml\r\n\r\n{4}\r\n=====\r\n\r\nRendered Source\r\n\r\n{5}", GetViewContents("Partials"), GetViewContents("_ParentPartial"), GetViewContents("_ChildPartial"), + GetViewContents("_BaseParentPartial"), + GetViewContents("_BaseChildPartial"), renderedSource)); } diff --git a/ChameleonForms.Example/ChameleonForms.Example.csproj b/ChameleonForms.Example/ChameleonForms.Example.csproj index 566d4eeb..34f3ebf6 100644 --- a/ChameleonForms.Example/ChameleonForms.Example.csproj +++ b/ChameleonForms.Example/ChameleonForms.Example.csproj @@ -23,6 +23,7 @@ ..\ true + true @@ -208,6 +209,8 @@ + + diff --git a/ChameleonForms.Example/Controllers/ExampleFormsController.cs b/ChameleonForms.Example/Controllers/ExampleFormsController.cs index 10733a35..f489429e 100644 --- a/ChameleonForms.Example/Controllers/ExampleFormsController.cs +++ b/ChameleonForms.Example/Controllers/ExampleFormsController.cs @@ -236,7 +236,26 @@ public class ParentViewModel public ChildViewModel Child { get; set; } } - public class ViewModelExample + public class ViewModelExampleBase + { + public ViewModelExampleBase() + { + BaseChild = new ViewModelExampleBaseChild(); + } + + public string BaseField { get; set; } + public string BaseField2 { get; set; } + + public ViewModelExampleBaseChild BaseChild { get; set; } + } + + public class ViewModelExampleBaseChild + { + public string BaseChildField { get; set; } + public string BaseChildField2 { get; set; } + } + + public class ViewModelExample : ViewModelExampleBase { public ViewModelExample() { diff --git a/ChameleonForms.Example/Views/ExampleForms/Partials.cshtml b/ChameleonForms.Example/Views/ExampleForms/Partials.cshtml index b0dacea4..f93e9b2c 100644 --- a/ChameleonForms.Example/Views/ExampleForms/Partials.cshtml +++ b/ChameleonForms.Example/Views/ExampleForms/Partials.cshtml @@ -9,14 +9,18 @@ @using (var f = Html.BeginChameleonForm()) { @f.Partial("_ParentPartial") + @f.Partial("_BaseParentPartial") @f.PartialFor(m => m.Child, "_ChildPartial") - + @f.PartialFor(m => m.BaseChild, "_BaseChildPartial") + using (var s = f.BeginSection("This is in the parent view")) { @s.FieldFor(m => m.Decimal).Append("in parent view") @s.Partial("_ParentPartial") + @s.Partial("_BaseParentPartial") @s.FieldFor(m => m.ListId).Append("in parent view") @s.PartialFor(m => m.Child, "_ChildPartial") + @s.PartialFor(m => m.BaseChild, "_BaseChildPartial") @s.FieldFor(m => m.SomeCheckbox).Append(" in parent view") } diff --git a/ChameleonForms.Example/Views/ExampleForms/_BaseChildPartial.cshtml b/ChameleonForms.Example/Views/ExampleForms/_BaseChildPartial.cshtml new file mode 100644 index 00000000..9554080e --- /dev/null +++ b/ChameleonForms.Example/Views/ExampleForms/_BaseChildPartial.cshtml @@ -0,0 +1,12 @@ +@model ViewModelExampleBaseChild + +@if (this.IsInFormSection()) +{ + @:@this.FormSection().FieldFor(m => m.BaseChildField).Append("From partial against child model in the base class") +} +else +{ + using (var s = this.Form().BeginSection("This is from a form-level partial against a child model in the base class")) { + @s.FieldFor(m => m.BaseChildField2) + } +} diff --git a/ChameleonForms.Example/Views/ExampleForms/_BaseParentPartial.cshtml b/ChameleonForms.Example/Views/ExampleForms/_BaseParentPartial.cshtml new file mode 100644 index 00000000..730474d3 --- /dev/null +++ b/ChameleonForms.Example/Views/ExampleForms/_BaseParentPartial.cshtml @@ -0,0 +1,12 @@ +@model ViewModelExampleBase + +@if (this.IsInFormSection()) +{ + @:@this.FormSection().FieldFor(m => m.BaseField).Append("from partial against top-level model casted to base class") +} +else +{ + using (var s = this.Form().BeginSection("This is from a form-level partial against the top-level model casted to base class")) { + @s.FieldFor(m => m.BaseField2) + } +} diff --git a/ChameleonForms.Example/Views/ExampleForms/_ChildPartial.cshtml b/ChameleonForms.Example/Views/ExampleForms/_ChildPartial.cshtml index f724ccf0..4614fed6 100644 --- a/ChameleonForms.Example/Views/ExampleForms/_ChildPartial.cshtml +++ b/ChameleonForms.Example/Views/ExampleForms/_ChildPartial.cshtml @@ -1,5 +1,7 @@ @model ChildViewModel +

This should show the ID of the child field including the parent path: @Html.IdFor(m => m.ChildField)

+ @if (this.IsInFormSection()) { @:@this.FormSection().FieldFor(m => m.ChildField).Append("From partial against child model") diff --git a/ChameleonForms/Form.cs b/ChameleonForms/Form.cs index a86eb073..90430ce3 100644 --- a/ChameleonForms/Form.cs +++ b/ChameleonForms/Form.cs @@ -6,6 +6,7 @@ using ChameleonForms.Enums; using ChameleonForms.FieldGenerators; using ChameleonForms.Templates; +using ChameleonForms.Utils; using JetBrains.Annotations; namespace ChameleonForms @@ -104,6 +105,13 @@ public void Dispose() public IForm CreatePartialForm(object partialModelExpression, HtmlHelper partialViewHelper) { var partialModelAsExpression = partialModelExpression as Expression>; + if (partialModelAsExpression == null + && typeof(TPartialModel).IsAssignableFrom(typeof(TModel)) + && partialModelExpression is Expression>) + { + var partialModelAsUnboxedExpression = partialModelExpression as Expression>; + partialModelAsExpression = partialModelAsUnboxedExpression.AddCast(); + } return new PartialViewForm(this, partialViewHelper, partialModelAsExpression); } } diff --git a/ChameleonForms/Utils/ExpressionExtensions.cs b/ChameleonForms/Utils/ExpressionExtensions.cs index 04cbee3f..1719d5b2 100644 --- a/ChameleonForms/Utils/ExpressionExtensions.cs +++ b/ChameleonForms/Utils/ExpressionExtensions.cs @@ -15,6 +15,14 @@ public static Expression> Combine(this Ex var body = visitor.Visit(nav.Body); return Expression.Lambda>(body, param); } + + public static Expression> AddCast(this Expression> expression) + { + Expression converted = Expression.Convert(expression.Body, typeof(TOutput)); + + return Expression.Lambda> + (converted, expression.Parameters); + } } class ReplacementVisitor : ExpressionVisitor diff --git a/docs/field-configuration.md b/docs/field-configuration.md index 80b7ceed..fd1d8662 100644 --- a/docs/field-configuration.md +++ b/docs/field-configuration.md @@ -547,4 +547,18 @@ This works because: * The `IFieldConfiguration` interface extends `IHtmlString`, which forces it to implement the `.ToHtmlString()` method (which will be called by razor via the `@` operator) * All the methods on `IFieldConfiguration` return the same instance of the `IFieldConfiguration` object so the `@` operator will apply to that Field Configuration regardless of what methods the user calls * The `SetField(IHtmlString)` method or the `SetField(Func)` method will be called before returning the `IFieldConfiguration` to indicate what HTML should be output by the Field Configuration when the `.ToHtmlString()` method is called -* The `SetField` method approach allows for lazy evaluation of the HTML to output, meaning the HTML generation can occur after all of the `IFieldConfiguration` methods have been called (allowing the Field Configuration to be mutated before eventually being used) \ No newline at end of file +* The `SetField` method approach allows for lazy evaluation of the HTML to output, meaning the HTML generation can occur after all of the `IFieldConfiguration` methods have been called (allowing the Field Configuration to be mutated before eventually being used) + +Passing HTML to field configuration methods +------------------------------------------- + +For all the field configuration methods that take an `IHtmlString` you have a few options available to you: + +* Pass the HTML as a string e.g. `.Label(new HtmlString("My label"))` +* Pass the HTML by calling any method that returns an `IHtmlString` including a razor helper e.g.: `.Label(GetLabelHtml())` +``` + @helper GetLabelHtml() { + My label + } +``` +* Passing the HTML inline using the `Func` extension method overloads e.g. `.Label(@My label)`