Skip to content

Commit

Permalink
Merge pull request #155 from MRCollective/partial-fixes
Browse files Browse the repository at this point in the history
Partial fixes
  • Loading branch information
robdmoore authored Jan 28, 2017
2 parents bf0521d + 5ff55b2 commit 1c4589d
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ <h1>Partials</h1>
@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")
}

Expand Down Expand Up @@ -51,6 +55,8 @@ <h1>Partials</h1>

@model ChildViewModel

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

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

=====

_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

<form action="" method="post" novalidate="novalidate"> <fieldset>
Expand All @@ -75,6 +115,17 @@ <h1>Partials</h1>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>This is from a form-level partial against the top-level model casted to base class</legend>
<dl>
<dt><label for="BaseField2">Base field 2</label></dt>
<dd>
<input id="BaseField2" name="BaseField2" type="text" value=""> <span class="field-validation-valid" data-valmsg-for="BaseField2" data-valmsg-replace="true"></span>
</dd>
</dl>
</fieldset>
<p>This should show the ID of the child field including the parent path: Child_ChildField</p>

<fieldset>
<legend>This is from a form-level partial against a child model</legend>
<dl>
Expand All @@ -87,6 +138,15 @@ <h1>Partials</h1>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>This is from a form-level partial against a child model in the base class</legend>
<dl>
<dt><label for="BaseChild_BaseChildField2">Base child field 2</label></dt>
<dd>
<input id="BaseChild_BaseChildField2" name="BaseChild.BaseChildField2" type="text" value=""> <span class="field-validation-valid" data-valmsg-for="BaseChild.BaseChildField2" data-valmsg-replace="true"></span>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>This is in the parent view</legend>
<dl>
Expand All @@ -99,17 +159,29 @@ <h1>Partials</h1>
<textarea cols="20" id="TextAreaField" name="TextAreaField" rows="2">Initial value</textarea>from partial against top-level model <span class="field-validation-valid" data-valmsg-for="TextAreaField" data-valmsg-replace="true"></span>
</dd>

<dt><label for="BaseField">Base field</label></dt>
<dd>
<input id="BaseField" name="BaseField" type="text" value="">from partial against top-level model casted to base class <span class="field-validation-valid" data-valmsg-for="BaseField" data-valmsg-replace="true"></span>
</dd>

<dt><label for="ListId">List id</label> <em class="required">*</em></dt>
<dd>
<select data-val="true" data-val-number="The field List id must be a number." data-val-required="The List id field is required." id="ListId" name="ListId"><option value="1">A</option>
<option value="2">B</option>
</select>in parent view <span class="field-validation-valid" data-valmsg-for="ListId" data-valmsg-replace="true"></span>
</dd>
<p>This should show the ID of the child field including the parent path: Child_ChildField</p>

<dt><label for="Child_ChildField">Child field</label> <em class="required">*</em></dt>
<dd>
<input data-val="true" data-val-required="The Child field field is required." id="Child_ChildField" name="Child.ChildField" type="text" value="">From partial against child model <span class="field-validation-valid" data-valmsg-for="Child.ChildField" data-valmsg-replace="true"></span>
</dd>

<dt><label for="BaseChild_BaseChildField">Base child field</label></dt>
<dd>
<input id="BaseChild_BaseChildField" name="BaseChild.BaseChildField" type="text" value="">From partial against child model in the base class <span class="field-validation-valid" data-valmsg-for="BaseChild.BaseChildField" data-valmsg-replace="true"></span>
</dd>

<dt><label for="SomeCheckbox">Some checkbox</label> <em class="required">*</em></dt>
<dd>
<input data-val="true" data-val-required="The Some checkbox field is required." id="SomeCheckbox" name="SomeCheckbox" type="checkbox" value="true"> <label for="SomeCheckbox">Some checkbox</label> in parent view <span class="field-validation-valid" data-valmsg-for="SomeCheckbox" data-valmsg-replace="true"></span>
Expand Down
6 changes: 3 additions & 3 deletions ChameleonForms.AcceptanceTests/PartialForTests.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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));
}

Expand Down
3 changes: 3 additions & 0 deletions ChameleonForms.Example/ChameleonForms.Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<TargetFrameworkProfile />
<RestorePackages>true</RestorePackages>
<UseGlobalApplicationHostFile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down Expand Up @@ -208,6 +209,8 @@
<Content Include="Views\ExampleForms\Partials.cshtml" />
<Content Include="Views\ExampleForms\_ChildPartial.cshtml" />
<Content Include="Views\ExampleForms\ChangingContext.cshtml" />
<Content Include="Views\ExampleForms\_BaseParentPartial.cshtml" />
<Content Include="Views\ExampleForms\_BaseChildPartial.cshtml" />
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
Expand Down
21 changes: 20 additions & 1 deletion ChameleonForms.Example/Controllers/ExampleFormsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
6 changes: 5 additions & 1 deletion ChameleonForms.Example/Views/ExampleForms/Partials.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
12 changes: 12 additions & 0 deletions ChameleonForms.Example/Views/ExampleForms/_BaseChildPartial.cshtml
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@model ChildViewModel

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

@if (this.IsInFormSection())
{
@:@this.FormSection().FieldFor(m => m.ChildField).Append("From partial against child model")
Expand Down
8 changes: 8 additions & 0 deletions ChameleonForms/Form.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using ChameleonForms.Enums;
using ChameleonForms.FieldGenerators;
using ChameleonForms.Templates;
using ChameleonForms.Utils;
using JetBrains.Annotations;

namespace ChameleonForms
Expand Down Expand Up @@ -104,6 +105,13 @@ public void Dispose()
public IForm<TPartialModel> CreatePartialForm<TPartialModel>(object partialModelExpression, HtmlHelper<TPartialModel> partialViewHelper)
{
var partialModelAsExpression = partialModelExpression as Expression<Func<TModel, TPartialModel>>;
if (partialModelAsExpression == null
&& typeof(TPartialModel).IsAssignableFrom(typeof(TModel))
&& partialModelExpression is Expression<Func<TModel, TModel>>)
{
var partialModelAsUnboxedExpression = partialModelExpression as Expression<Func<TModel, TModel>>;
partialModelAsExpression = partialModelAsUnboxedExpression.AddCast<TModel, TPartialModel>();
}
return new PartialViewForm<TModel, TPartialModel>(this, partialViewHelper, partialModelAsExpression);
}
}
Expand Down
8 changes: 8 additions & 0 deletions ChameleonForms/Utils/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ public static Expression<Func<T, TProperty>> Combine<T, TNav, TProperty>(this Ex
var body = visitor.Visit(nav.Body);
return Expression.Lambda<Func<T, TProperty>>(body, param);
}

public static Expression<Func<TInput, TOutput>> AddCast<TInput, TOutput>(this Expression<Func<TInput, TInput>> expression)
{
Expression converted = Expression.Convert(expression.Body, typeof(TOutput));

return Expression.Lambda<Func<TInput, TOutput>>
(converted, expression.Parameters);
}
}

class ReplacementVisitor : ExpressionVisitor
Expand Down
16 changes: 15 additions & 1 deletion docs/field-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<IHtmlString>)` 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)
* 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("<strong>My label</strong>"))`
* Pass the HTML by calling any method that returns an `IHtmlString` including a razor helper e.g.: `.Label(GetLabelHtml())`
```
@helper GetLabelHtml() {
<strong>My label</strong>
}
```
* Passing the HTML inline using the `Func<object, IHtml>` extension method overloads e.g. `.Label(@<strong>My label</strong>)`

0 comments on commit 1c4589d

Please sign in to comment.