Skip to content

Commit

Permalink
PartialFor implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
zabulus committed Nov 17, 2014
1 parent 19223f4 commit fcea6f3
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 6 deletions.
3 changes: 3 additions & 0 deletions ChameleonForms.Example/ChameleonForms.Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@
<Content Include="Views\Comparison\EditorTemplates\_Layout.cshtml" />
<Content Include="Views\Comparison\EditorTemplates\MembershipType.cshtml" />
<Content Include="Views\Shared\_BootstrapLayout.cshtml" />
<Content Include="Views\ExampleForms\PartialFor.cshtml" />
<Content Include="Views\ExampleForms\EditorTemplates\PartialViewModel.cshtml" />
<Content Include="Views\ExampleForms\EditorTemplates\ChildView.cshtml" />
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
Expand Down
5 changes: 5 additions & 0 deletions ChameleonForms.Example/Controllers/ExampleFormsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ public ActionResult Buttons()
{
return View();
}

public ActionResult PartialFor()
{
return this.View(new ViewModelExample());
}
}

public class ModelBindingViewModel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@model ChameleonForms.Example.Controllers.ChildViewModel

@{
ViewBag.Title = "ChildView";
}

<h2>ChildView</h2>

@this.ChameleonSection().FieldFor(x => x.ChildField)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@model ViewModelExample

@this.ChameleonSection().PartialFor(x => x.Child, "ChildView")
@this.ChameleonSection().FieldFor(x => x.SomeCheckbox)
16 changes: 16 additions & 0 deletions ChameleonForms.Example/Views/ExampleForms/PartialFor.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@model ViewModelExample

@{
ViewBag.Title = "ViewUsingPartial";
Layout = "~/Views/Shared/_BootstrapLayout.cshtml";
}

<h2>ViewUsingPartial</h2>

@using (var f = Html.BeginChameleonForm())
{
using (var s = f.BeginSection())
{
@s.PartialFor(x => x, "PartialViewModel");
}
}
4 changes: 3 additions & 1 deletion ChameleonForms.Example/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@model dynamic

@model dynamic

@{
ViewBag.Title = "ChameleonForms";
Expand Down Expand Up @@ -27,6 +28,7 @@
<li>@Html.ActionLink("Null model with list", "NullModelWithList", "ExampleForms")</li>
<li>@Html.ActionLink("Null list", "NullList", "ExampleForms")</li>
<li>@Html.ActionLink("Buttons", "Buttons", "ExampleForms")</li>
<li>@Html.ActionLink("PartialFor", "PartialFor", "ExampleForms")</li>
</ul>

<h2>Random forms and UI tests</h2>
Expand Down
2 changes: 2 additions & 0 deletions ChameleonForms/ChameleonForms.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<Compile Include="Component\Field.cs" />
<Compile Include="Component\FormComponent.cs" />
<Compile Include="Component\Message.cs" />
<Compile Include="Component\PartialSection.cs" />
<Compile Include="Enums\DisplayType.cs" />
<Compile Include="Enums\MessageType.cs" />
<Compile Include="Component\Section.cs" />
Expand Down Expand Up @@ -129,6 +130,7 @@
<DependentUpon>TwitterBootstrapHtmlHelpers.cshtml</DependentUpon>
</Compile>
<Compile Include="Templates\TwitterBootstrap3\ButtonHtmlAttributesExtensions.cs" />
<Compile Include="WebViewPageExtensions.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down
76 changes: 76 additions & 0 deletions ChameleonForms/Component/PartialSection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using ChameleonForms.Component.Config;
using ChameleonForms.Templates;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc.Html;

namespace ChameleonForms.Component
{
class PartialSection<TModel, TChild, TTemplate> : ISection<TChild> where TTemplate : IFormTemplate
{
private Section<TModel, TTemplate> section;
private Expression<Func<TModel, TChild>> parEx;

public PartialSection(Section<TModel, TTemplate> section, Expression<Func<TModel, TChild>> parEx)
{
this.section = section;
this.parEx = parEx;
}

public IFieldConfiguration FieldFor<TProperty>(Expression<Func<TChild, TProperty>> expression)
{
return this.section.FieldFor(ExpressionExtensions.Combine(parEx, expression));
}

public IHtmlString PartialFor<TChild2>(Expression<Func<TChild, TChild2>> expression)
{
return this.section.PartialFor(ExpressionExtensions.Combine(parEx, expression));
}

public IHtmlString PartialFor<TChild2>(Expression<Func<TChild, TChild2>> expression, string templateName)
{
return this.section.PartialFor(ExpressionExtensions.Combine(parEx, expression), templateName);
}
}

/// <summary>
/// Extensions to add partial for for sections
/// </summary>
public static class PartialExtensions
{
/// <summary>
/// Extension injects parent section into editor view
/// </summary>
/// <typeparam name="TModel">The view model type for the current view</typeparam>
/// <typeparam name="TTemplate">The type of HTML template renderer the form is using</typeparam>
/// <typeparam name="TValue">The view model type for the nested view</typeparam>
/// <param name="section">Form section</param>
/// <param name="expression">A lamdba expression to identify the field to render the field for</param>
/// <param name="templateName">The name of the template to use to render the object.</param>
/// <returns>Rendered view</returns>
public static IHtmlString PartialFor<TModel, TTemplate, TValue>(this Section<TModel, TTemplate> section, Expression<Func<TModel, TValue>> expression, string templateName) where TTemplate : IFormTemplate
{
object newViewData = new { ChameleonSection = section, ChameleonExpression = expression };
return section.Form.HtmlHelper.EditorFor(expression, templateName, null, newViewData);
}

/// <summary>
/// Extension injects parent section into editor view
/// </summary>
/// <typeparam name="TModel">The view model type for the current view</typeparam>
/// <typeparam name="TTemplate">The type of HTML template renderer the form is using</typeparam>
/// <typeparam name="TValue">The view model type for the nested view</typeparam>
/// <param name="section">Form section</param>
/// <param name="expression">A lamdba expression to identify the field to render the field for</param>
/// <returns>Rendered view</returns>
public static IHtmlString PartialFor<TModel, TTemplate, TValue>(this Section<TModel, TTemplate> section, Expression<Func<TModel, TValue>> expression) where TTemplate : IFormTemplate
{
object newViewData = new { ChameleonSection = section, ChameleonExpression = expression };
return section.Form.HtmlHelper.EditorFor(expression, null, null, newViewData);
}
}
}
86 changes: 84 additions & 2 deletions ChameleonForms/Component/Section.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,60 @@
using System.Web.Mvc;
using ChameleonForms.Component.Config;
using ChameleonForms.Templates;
using System.Web.Mvc.Html;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Dynamic;
using System.Collections;
using System.Collections.Generic;
using System.Web.Routing;

namespace ChameleonForms.Component
{
interface ISection
{
ISection<TChild> CreateChildSection<TChild>(object parentExpression);
}

/// <summary>
///
/// </summary>
/// <typeparam name="TModel"></typeparam>
public interface ISection<TModel>
{
/// <summary>
///
/// </summary>
/// <typeparam name="TChild"></typeparam>
/// <param name="expression"></param>
/// <returns></returns>
IFieldConfiguration FieldFor<TChild>(Expression<Func<TModel, TChild>> expression);

/// <summary>
///
/// </summary>
/// <typeparam name="TChild"></typeparam>
/// <param name="expression"></param>
/// <returns></returns>
IHtmlString PartialFor<TChild>(Expression<Func<TModel, TChild>> expression);

/// <summary>
///
/// </summary>
/// <typeparam name="TChild"></typeparam>
/// <param name="expression"></param>
/// <param name="templateName"></param>
/// <returns></returns>
IHtmlString PartialFor<TChild>(Expression<Func<TModel, TChild>> expression, string templateName);
}

/// <summary>
/// Wraps the output of a form section.
/// </summary>
/// <typeparam name="TModel">The view model type for the current view</typeparam>
/// <typeparam name="TTemplate">The type of HTML template renderer the form is using</typeparam>
public class Section<TModel, TTemplate> : FormComponent<TModel, TTemplate> where TTemplate : IFormTemplate
public class Section<TModel, TTemplate> : FormComponent<TModel, TTemplate>, ISection where TTemplate : IFormTemplate
{
private readonly IHtmlString _heading;
private readonly bool _nested;
Expand All @@ -25,7 +70,8 @@ public class Section<TModel, TTemplate> : FormComponent<TModel, TTemplate> where
/// <param name="nested">Whether the section is nested within another section</param>
/// <param name="leadingHtml">Any HTML to output at the start of the section</param>
/// <param name="htmlAttributes">Any HTML attributes to apply to the section container</param>
public Section(IForm<TModel, TTemplate> form, IHtmlString heading, bool nested, IHtmlString leadingHtml = null, HtmlAttributes htmlAttributes = null) : base(form, false)
public Section(IForm<TModel, TTemplate> form, IHtmlString heading, bool nested, IHtmlString leadingHtml = null, HtmlAttributes htmlAttributes = null)
: base(form, false)
{
_heading = heading;
_nested = nested;
Expand Down Expand Up @@ -61,6 +107,42 @@ public override IHtmlString End()
{
return _nested ? Form.Template.EndNestedSection() : Form.Template.EndSection();
}

ISection<TChild> ISection.CreateChildSection<TChild>(object parentExpression)
{
Expression<Func<TModel, TChild>> parEx = parentExpression as Expression<Func<TModel, TChild>>;
return new PartialSection<TModel, TChild, TTemplate>(this, parEx);
}
}

static class ExpressionExtensions
{
public static Expression<Func<T, TProperty>> Combine<T, TNav, TProperty>(Expression<Func<T, TNav>> parent, Expression<Func<TNav, TProperty>> nav)
{
var param = Expression.Parameter(typeof(T), "x");
var visitor = new ReplacementVisitor(parent.Parameters[0], param);
var newParentBody = visitor.Visit(parent.Body);
visitor = new ReplacementVisitor(nav.Parameters[0], newParentBody);
var body = visitor.Visit(nav.Body);
return Expression.Lambda<Func<T, TProperty>>(body, param);
}
}
class ReplacementVisitor : System.Linq.Expressions.ExpressionVisitor
{
private readonly Expression _oldExpr;
private readonly Expression _newExpr;
public ReplacementVisitor(Expression oldExpr, Expression newExpr)
{
_oldExpr = oldExpr;
_newExpr = newExpr;
}

public override Expression Visit(Expression node)
{
if (node == _oldExpr)
return _newExpr;
return base.Visit(node);
}
}

/// <summary>
Expand Down
41 changes: 41 additions & 0 deletions ChameleonForms/WebViewPageExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using ChameleonForms;
using ChameleonForms.Component;
using ChameleonForms.Templates;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace System.Web.Mvc
{
/// <summary>
/// Extension to use in nested partial view.
/// </summary>
public static class WebViewPageExtensions
{
/// <summary>
/// Get section from upper view
/// </summary>
/// <typeparam name="TModel">View model of nested partial view</typeparam>
/// <param name="self">View page for nested view</param>
/// <returns>Returns section of parent view</returns>
public static ISection<TModel> ChameleonSection<TModel>(this WebViewPage<TModel> self)
{
object parentSectionObject;
if (!self.ViewData.TryGetValue("ChameleonSection", out parentSectionObject))
{
throw new InvalidOperationException("Chameleon Section is unavailable for now");
}

object parentExpression;
if (!self.ViewData.TryGetValue("ChameleonExpression", out parentExpression))
{
throw new InvalidOperationException("Chameleon Section is unavailable for now");
}

ISection parentSection = parentSectionObject as ISection;
return parentSection.CreateChildSection<TModel>(parentExpression);
}
}
}
6 changes: 3 additions & 3 deletions ChameleonForms/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<packages>
<package id="GitHubFlowVersion" version="1.1.8" targetFramework="net40" />
<package id="Humanizer" version="1.28.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Mvc" version="4.0.20710.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Razor" version="2.0.20715.0" targetFramework="net40" />
<package id="Microsoft.AspNet.WebPages" version="2.0.20710.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net40" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net40" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net40" />
<package id="MSBuildTasks" version="1.4.0.65" targetFramework="net40" />
</packages>

4 comments on commit fcea6f3

@MRCollectiveCI
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity ChameleonForms :: Continuous Integration Build 198 is now running

@MRCollectiveCI
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity ChameleonForms :: Continuous Integration Build 1.1.1-PullRequest104+015 outcome was SUCCESS
Summary: Tests passed: 2997 Build time: 0:0:0

@MRCollectiveCI
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity ChameleonForms :: Continuous Integration Build 205 is now running

@MRCollectiveCI
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TeamCity ChameleonForms :: Continuous Integration Build 1.2.1-PullRequest104+001 outcome was SUCCESS
Summary: Tests passed: 819 Build time: 0:0:0

Please sign in to comment.