Skip to content

Commit 869a835

Browse files
authored
Merge 825c6d7 into e62459a
2 parents e62459a + 825c6d7 commit 869a835

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+728
-18
lines changed

ChameleonForms.Core/Templates/IFormTemplate.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ public interface IFormTemplate
3333
/// <param name="method">The form method</param>
3434
/// <param name="htmlAttributes">Any HTML attributes the form should use; specified as an anonymous object</param>
3535
/// <param name="enctype">The encoding type for the form</param>
36+
/// <param name="formSubmitted">Whether or not the form has been submitted i.e. it's a post back request</param>
3637
/// <returns>The starting HTML for a form</returns>
37-
IHtmlContent BeginForm(string action, FormMethod method, HtmlAttributes htmlAttributes, EncType? enctype);
38+
IHtmlContent BeginForm(string action, FormMethod method, HtmlAttributes htmlAttributes, EncType? enctype, bool formSubmitted);
3839

3940
/// <summary>
4041
/// Creates the ending HTML for a form.

ChameleonForms.Example/Startup.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using ChameleonForms.Example.Controllers.Filters;
22
using ChameleonForms.Templates;
3+
using ChameleonForms.Templates.Bootstrap4;
34
using ChameleonForms.Templates.Default;
45
using ChameleonForms.Templates.TwitterBootstrap3;
56
using Microsoft.AspNetCore.Builder;
@@ -38,6 +39,9 @@ public void ConfigureServices(IServiceCollection services)
3839
if (template.StartsWith("default"))
3940
return new DefaultFormTemplate();
4041

42+
if (template == "bootstrap4")
43+
return new Bootstrap4FormTemplate();
44+
4145
return new TwitterBootstrap3FormTemplate();
4246
});
4347
services.AddChameleonForms(b => b.WithoutTemplateTypeRegistration());

ChameleonForms.Example/Views/Home/Index.cshtml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
<ul>
1212
<li><a href="?template=default">Default</a></li>
1313
<li><a href="?template=defaultnojquery">Default - without jQuery</a></li>
14-
<li><a href="?template=bootstrap">Twitter Bootstrap</a></li>
15-
<li><a href="?template=bootstrapnojquery">Twitter Bootstrap - without jQuery</a></li>
14+
<li><a href="?template=bootstrap3">Twitter Bootstrap 3</a></li>
15+
<li><a href="?template=bootstrap3nojquery">Twitter Bootstrap 3 - without jQuery</a></li>
16+
<li><a href="?template=bootstrap4">Bootstrap 4</a></li>
1617
</ul>
1718

1819
<h2>ChameleonForms vs ASP.NET MVC <abbr title="out-of-the-box">OOTB</abbr></h2>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>@ViewBag.Title</title>
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
8+
</head>
9+
<body>
10+
<div class="container">
11+
@RenderBody()
12+
</div>
13+
<script type="text/javascript" src="~/lib/jquery/dist/jquery.min.js"></script>
14+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
15+
<script type="text/javascript" src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
16+
<script type="text/javascript" src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
17+
<script type="text/javascript" src="~/lib/jquery-validation-unobtrusive-bootstrap/unobtrusive-bootstrap.js"></script>
18+
<script type="text/javascript" src="~/lib/chameleonforms/unobtrusive-date-validation.chameleonforms.js"></script>
19+
</body>
20+
</html>

ChameleonForms.Example/Views/_ViewStart.cshtml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
{
2020
Layout = "~/Views/Shared/_Layout.cshtml";
2121
}
22+
else if (ViewContext.HttpContext.Request.Cookies["template"] == "bootstrap4")
23+
{
24+
Layout = "~/Views/Shared/_Bootstrap4Layout.cshtml";
25+
}
2226
else
2327
{
2428
Layout = "~/Views/Shared/_Bootstrap3Layout.cshtml";
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// https://github.com/brecons/jquery-validation-unobtrusive-bootstrap
2+
3+
(function ($) {
4+
if($.validator && $.validator.unobtrusive){
5+
var defaultOptions = {
6+
validClass: 'is-valid',
7+
errorClass: 'is-invalid',
8+
highlight: function (element, errorClass, validClass) {
9+
$(element)
10+
.removeClass(validClass)
11+
.addClass(errorClass);
12+
},
13+
unhighlight: function (element, errorClass, validClass) {
14+
$(element)
15+
.removeClass(errorClass)
16+
.addClass(validClass);
17+
}
18+
};
19+
20+
$.validator.setDefaults(defaultOptions);
21+
22+
$.validator.unobtrusive.options = {
23+
errorClass: defaultOptions.errorClass,
24+
validClass: defaultOptions.validClass,
25+
errorElement: 'div',
26+
errorPlacement: function (error, element) {
27+
error.addClass('invalid-feedback');
28+
29+
if (element.next().is(".input-group-append")) {
30+
error.insertAfter(element.next());
31+
} else {
32+
error.insertAfter(element);
33+
}
34+
}
35+
};
36+
}
37+
else {
38+
console.warn('$.validator is not defined. Please load this library **after** loading jquery.validate.js and jquery.validate.unobtrusive.js');
39+
}
40+
})(jQuery);
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using ChameleonForms.Component;
5+
using ChameleonForms.Component.Config;
6+
using ChameleonForms.Enums;
7+
using ChameleonForms.FieldGenerators;
8+
using ChameleonForms.FieldGenerators.Handlers;
9+
using ChameleonForms.Templates.ChameleonFormsBootstrap4Template;
10+
using ChameleonForms.Templates.ChameleonFormsBootstrap4Template.Params;
11+
using Humanizer;
12+
using Microsoft.AspNetCore.Html;
13+
using Microsoft.AspNetCore.Mvc.ModelBinding;
14+
using Microsoft.AspNetCore.Mvc.Rendering;
15+
using RazorRenderer;
16+
17+
namespace ChameleonForms.Templates.Bootstrap4
18+
{
19+
/// <summary>
20+
/// The default Chameleon Forms form template renderer.
21+
/// </summary>
22+
public class Bootstrap4FormTemplate : Default.DefaultFormTemplate
23+
{
24+
private static readonly IEnumerable<string> StyledButtonClasses = Enum.GetNames(typeof(ButtonStyle))
25+
.Select(x => x.Humanize())
26+
.ToArray();
27+
28+
private static readonly FieldDisplayType[] NormalFieldTypes = new[] { FieldDisplayType.DropDown, FieldDisplayType.SingleLineText, FieldDisplayType.MultiLineText };
29+
30+
/// <inheritdoc />
31+
public override void PrepareFieldConfiguration<TModel, T>(IFieldGenerator<TModel, T> fieldGenerator, IFieldGeneratorHandler<TModel, T> fieldGeneratorHandler, IFieldConfiguration fieldConfiguration, FieldParent fieldParent)
32+
{
33+
if (fieldParent == FieldParent.Form)
34+
return;
35+
36+
fieldConfiguration.InlineLabelWrapsElement();
37+
38+
fieldConfiguration.AddValidationClass("invalid-feedback");
39+
40+
var displayType = fieldGeneratorHandler.GetDisplayType(fieldConfiguration);
41+
if (NormalFieldTypes.Contains(displayType))
42+
{
43+
fieldConfiguration.Bag.CanBeInputGroup = true;
44+
fieldConfiguration.AddClass("form-control");
45+
}
46+
47+
if (displayType == FieldDisplayType.Checkbox)
48+
{
49+
fieldConfiguration.Bag.IsCheckboxControl = true;
50+
// Hide the parent label otherwise it looks weird
51+
fieldConfiguration.Label("").WithoutLabelElement();
52+
}
53+
54+
if (displayType == FieldDisplayType.List)
55+
fieldConfiguration.Bag.IsRadioOrCheckboxList = true;
56+
}
57+
58+
/// <inheritdoc />
59+
public override IHtmlContent BeginForm(string action, FormMethod method, HtmlAttributes htmlAttributes, EncType? enctype, bool formSubmitted)
60+
{
61+
if (formSubmitted)
62+
htmlAttributes.AddClass("was-validated");
63+
return HtmlCreator.BuildFormTag(action, method, htmlAttributes, enctype);
64+
}
65+
66+
/// <inheritdoc />
67+
public override IHtmlContent EndForm()
68+
{
69+
return new EndForm().Render();
70+
}
71+
72+
/// <inheritdoc />
73+
public override IHtmlContent BeginSection(IHtmlContent heading = null, IHtmlContent leadingHtml = null, HtmlAttributes htmlAttributes = null)
74+
{
75+
return new BeginSection().Render(new BeginSectionParams {Heading = heading, LeadingHtml = leadingHtml, HtmlAttributes = htmlAttributes ?? new HtmlAttributes() });
76+
}
77+
78+
/// <inheritdoc />
79+
public override IHtmlContent EndSection()
80+
{
81+
return new EndSection().Render();
82+
}
83+
84+
/// <inheritdoc />
85+
public override IHtmlContent BeginNestedSection(IHtmlContent heading = null, IHtmlContent leadingHtml = null, HtmlAttributes htmlAttributes = null)
86+
{
87+
return new BeginNestedSection().Render(new BeginSectionParams { Heading = heading, LeadingHtml = leadingHtml, HtmlAttributes = htmlAttributes ?? new HtmlAttributes() });
88+
}
89+
90+
/// <inheritdoc />
91+
public override IHtmlContent EndNestedSection()
92+
{
93+
return new EndNestedSection().Render();
94+
}
95+
96+
/// <inheritdoc />
97+
public override IHtmlContent Field(IHtmlContent labelHtml, IHtmlContent elementHtml, IHtmlContent validationHtml, ModelMetadata fieldMetadata, IReadonlyFieldConfiguration fieldConfiguration, bool isValid)
98+
{
99+
return new Field().Render(new FieldParams
100+
{
101+
RenderMode = FieldRenderMode.Field, LabelHtml = labelHtml, ElementHtml = elementHtml,
102+
ValidationHtml = validationHtml, FieldMetadata = fieldMetadata, FieldConfiguration = fieldConfiguration,
103+
IsValid = isValid, RequiredDesignator = RequiredDesignator(fieldMetadata, fieldConfiguration, isValid)
104+
});
105+
}
106+
107+
/// <inheritdoc />
108+
public override IHtmlContent BeginField(IHtmlContent labelHtml, IHtmlContent elementHtml, IHtmlContent validationHtml, ModelMetadata fieldMetadata, IReadonlyFieldConfiguration fieldConfiguration, bool isValid)
109+
{
110+
return new Field().Render(new FieldParams
111+
{
112+
RenderMode = FieldRenderMode.BeginField,
113+
LabelHtml = labelHtml,
114+
ElementHtml = elementHtml,
115+
ValidationHtml = validationHtml,
116+
FieldMetadata = fieldMetadata,
117+
FieldConfiguration = fieldConfiguration,
118+
IsValid = isValid,
119+
RequiredDesignator = RequiredDesignator(fieldMetadata, fieldConfiguration, isValid)
120+
});
121+
}
122+
123+
/// <inheritdoc />
124+
protected override IHtmlContent RequiredDesignator(ModelMetadata fieldMetadata, IReadonlyFieldConfiguration fieldConfiguration, bool isValid)
125+
{
126+
return new RequiredDesignator().Render();
127+
}
128+
129+
/// <inheritdoc />
130+
public override IHtmlContent EndField()
131+
{
132+
return new EndField().Render();
133+
}
134+
135+
/// <inheritdoc />
136+
public override IHtmlContent BeginMessage(MessageType messageType, IHtmlContent heading)
137+
{
138+
string alertType;
139+
switch (messageType)
140+
{
141+
case MessageType.Warning:
142+
alertType = "warning";
143+
break;
144+
case MessageType.Action:
145+
alertType = "primary";
146+
break;
147+
case MessageType.Failure:
148+
alertType = "danger";
149+
break;
150+
case MessageType.Success:
151+
alertType = "success";
152+
break;
153+
default:
154+
alertType = "info";
155+
break;
156+
}
157+
158+
return new BeginAlert().Render(new AlertParams {AlertType = alertType, Heading = heading });
159+
}
160+
161+
/// <inheritdoc />
162+
public override IHtmlContent EndMessage()
163+
{
164+
return new EndAlert().Render();
165+
}
166+
167+
/// <inheritdoc />
168+
public override IHtmlContent BeginNavigation()
169+
{
170+
return new BeginNavigation().Render();
171+
}
172+
173+
/// <inheritdoc />
174+
public override IHtmlContent EndNavigation()
175+
{
176+
return new EndNavigation().Render();
177+
}
178+
179+
/// <inheritdoc />
180+
public override IHtmlContent Button(IHtmlContent content, string type, string id, string value, HtmlAttributes htmlAttributes)
181+
{
182+
htmlAttributes = htmlAttributes ?? new HtmlAttributes();
183+
htmlAttributes.AddClass("btn");
184+
if (!StyledButtonClasses.Any(c => htmlAttributes.Attributes["class"].Contains(c)))
185+
htmlAttributes.AddClass("btn-light");
186+
187+
return base.Button(content, type, id, value, htmlAttributes);
188+
}
189+
190+
/// <inheritdoc />
191+
public override IHtmlContent RadioOrCheckboxList(IEnumerable<IHtmlContent> list, bool isCheckbox)
192+
{
193+
return new RadioOrCheckboxList().Render(new ListParams {Items = list, IsCheckbox = isCheckbox});
194+
}
195+
}
196+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using ChameleonForms.Component;
2+
using Humanizer;
3+
4+
namespace ChameleonForms.Templates.Bootstrap4
5+
{
6+
/// <summary>
7+
/// Extension methods on <see cref="HtmlAttributes"/> for the Bootstrap 4 template.
8+
/// </summary>
9+
public static class ButtonHtmlAttributesExtensions
10+
{
11+
/// <summary>
12+
/// Adds the given emphasis to the button.
13+
/// </summary>
14+
/// <example>
15+
/// @n.Submit("Submit").WithStyle(ButtonStyle.Warning)
16+
/// </example>
17+
/// <param name="attrs">The Html Attributes from a navigation button</param>
18+
/// <param name="style">The style of button</param>
19+
/// <returns>The Html Attribute object so other methods can be chained off of it</returns>
20+
public static ButtonHtmlAttributes WithStyle(this ButtonHtmlAttributes attrs, ButtonStyle style)
21+
{
22+
// ReSharper disable once MustUseReturnValue
23+
if (style != ButtonStyle.Default)
24+
attrs.AddClass(style.Humanize());
25+
return attrs;
26+
}
27+
28+
/// <summary>
29+
/// Changes the button to use the given size.
30+
/// </summary>
31+
/// <example>
32+
/// @n.Submit("Submit").WithSize(ButtonSize.Large)
33+
/// </example>
34+
/// <param name="attrs">The Html Attributes from a navigation button</param>
35+
/// <param name="size">The size of button</param>
36+
/// <returns>The Html Attribute object so other methods can be chained off of it</returns>
37+
public static ButtonHtmlAttributes WithSize(this ButtonHtmlAttributes attrs, ButtonSize size)
38+
{
39+
// ReSharper disable once MustUseReturnValue
40+
if (size != ButtonSize.Default && size != ButtonSize.NoneSpecified)
41+
attrs.AddClass($"btn-{size.Humanize()}");
42+
return attrs;
43+
}
44+
}
45+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.ComponentModel;
2+
3+
namespace ChameleonForms.Templates.Bootstrap4
4+
{
5+
/// <summary>
6+
/// Bootstrap 4 button sizes: https://getbootstrap.com/docs/4.5/components/buttons/#sizes
7+
/// </summary>
8+
public enum ButtonSize
9+
{
10+
/// <summary>
11+
/// None specified.
12+
/// </summary>
13+
[Description("")]
14+
NoneSpecified,
15+
/// <summary>
16+
/// Small button size.
17+
/// </summary>
18+
[Description("sm")]
19+
Small,
20+
/// <summary>
21+
/// Default button size.
22+
/// </summary>
23+
Default,
24+
/// <summary>
25+
/// Large button size.
26+
/// </summary>
27+
[Description("lg")]
28+
Large
29+
}
30+
}

0 commit comments

Comments
 (0)