Here are the steps I took to get forms with js support and the builtin css working. To summarize:
You need to provide your own jQuery as it is no longer included in module package distributed with the Forms nuget. You'll need to set the Forms.config file in the forms module as such.
<!-- /modules/_protected/EPiServer.Forms/Forms.config -->
<configuration>
...
<episerverforms
...
injectFormOwnJQuery="false"
...
>
...
</configuration>
These partial views reference the requisite static files. The code in these is based on an inspection of the Forms module's FormContainerBlockController
@*** /Views/Shared/_FormsHeader.cshtml ***@
@using EPiServer.Web
@using EPiServer.Forms.Controllers
@using System.Threading
@{
// Different resources are needed in edit mode
ContextMode currentMode = ServiceLocator.Current.GetInstance<IContextModeResolver>().CurrentMode;
bool isNotEdit = currentMode is ContextMode.Default or ContextMode.Preview;
}
@if (isNotEdit)
{
// Use instance methods from a FormContainerBlockController
var formContainerBlockController = new FormContainerBlockController();
// Despite its name this is simply js code that ensures a global epi.EPiServer.Forms object is defined
var originalJQuery = formContainerBlockController.GetOriginalJQuery();
/* The prerequisite script does a couple things:
* 1. Sets a jQuery instance to the global variable $$epiforms, either provided by Opti or you
* 2. Adds properties to the global epi.EPiServer object that
* a) indicate the current page link, page language, and form language and
* b) configure values for the Forms object from the Forms.config file or editor's settings
*/
var prerequisiteScript = formContainerBlockController.GetPrerequisiteScript(
new List<string>(), // additional script resources
new List<string>(), // additional style resources
Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName);
<link rel="stylesheet" type="text/css" data-f-resource="EPiServerForms.css" href="/Util/EPiServer.Forms/EPiServerForms.css"/>
@* Use jQuery from someplace else if you don't have your own jQuery *@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"
integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"></script>
<script type="text/javascript">
@Html.Raw(originalJQuery)
@Html.Raw(prerequisiteScript)
@* I'm not certain why this is needed. Either I missed something, or forms appears to delete the global '$' variable.
* Either way, the global jQuery remains. *@
window.$ = jQuery
</script>
}
else
{
<link rel="stylesheet" type="text/css" data-f-resource="EPiServerForms.css" href="/Util/EPiServer.Forms/EPiServerForms_EditView.css"/>
}
@*** /Views/Shared/_FormsFooter.cshtml ***@
@using EPiServer.Web
@{
ContextMode currentMode = ServiceLocator.Current.GetInstance<IContextModeResolver>().CurrentMode;
bool isNotEdit = currentMode is ContextMode.Default or ContextMode.Preview;
}
@if (isNotEdit)
{
<script src="/Util/EPiServer.Forms/EPiServerForms.js" ></script>
}
The first goes in the <head> section and the second at the end of <body>.
This fixes a different, but related problem to the missing static resources. The reason that these resources are not rendered in the @Html.RenderRequiredResources(string) invocation is that the registration of the resources happens in FormContainerBlockControler.InvokeComponent. Again, this after the page and layout have been rendered. Similarly, the AntiForgeryToken is generated after the layout has been rendered, but more importantly after the response has begun to be written to. Because AntiForgeryToken sets a cookie, it attempts to alter the headers and this causes an exception after the response writing begins.
If the AntiForgeryToken invocation is already used on every page that will have Forms on it (eg. it's in your footer's newsletter signup form) then you don't need to add it.
When using ajax submission the current code expects camelCase instead of PascalCase. The client-side code therefore doesn't detect a successful submission.
// Startup.cs
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<JsonOptions>(o =>
{
o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
}
I have the same issues when rendering my form in a contentarea, no js or css when I install episerver.forms on a blank new site.
It has only 1 pagetype "StartPage" containing a contentarea where I add the form. Then I have a simple _Layout.cshtml with @RenderBody(), @Html.RequiredClientResources("Footer") & @Html.RequiredClientResources("Header ").
Could it be affected by this bug perhaps? https://world.optimizely.com/support/Bug-list/bug/CMS-21308 .
But Im going to try your workaround!
EPiServer.CMS.Core 12.4.0 was released 22 feb but if your using the Episerver.CMS package (12.3.1) it wont pull the new version.
Im hoping they'll update that soon, until then in my local dev environment I got it to work by manually adding these to the csproj:
<PackageReference Include="EPiServer.CMS.AspNetCore.Templating" Version="12.4.0" />
<PackageReference Include="EPiServer.CMS.AspNetCore" Version="12.4.0" />
<PackageReference Include="EPiServer.Hosting" Version="12.4.0" />
<PackageReference Include="EPiServer.CMS.UI" Version="12.3.1" />
<PackageReference Include="EPiServer.CMS.UI.Core" Version="12.3.1" />
<PackageReference Include="EPiServer.Framework" Version="12.4.0" />
<PackageReference Include="EPiServer.Framework.AspNetCore" Version="12.4.0" />
<PackageReference Include="EPiServer.CMS.AspNetCore.Routing" Version="12.4.0" />
<PackageReference Include="EPiServer.CMS.AspNetCore.Mvc" Version="12.4.0" />
<PackageReference Include="EPiServer.CMS.AspNetCore.HtmlHelpers" Version="12.4.0" />
but this will def. not go into main branch :P
I don't see the required resources for a FormContainerBlock rendered in the layout when there is a form property on the page. For example, EPiServerForms.css, EPiServerForms.js, or the filled-in template for EPiServerForms_prerequisite.js. They should be rendered in the layout with
@Html.RequiredClientResources("Header")
@Html.RequiredClientResources("Footer")
However, there is nothing rendered for forms in these areas. There are resources rendered here for Epi Search and Navigation (find).
If I manually add references to the resources and use the FormContainerBlockController's GetPrerequisiteScript to render that, then the form almost works as expected.
Debugging through the source code it seems that the FormContentAreaController in EPiServer.Forms.dll has its Invoke method called after the Layout has been rendered. The registration of resources is performed in Invoke, so the registration is made too late for the resources to be rendered.
We're using a _WrapperLayout setup, with sections forwarded to the wrapper from an inner layout. However removing this setup and rendering everything with 1 layout doesn't seem to change the behavior.
Has anyone else witnessed this behavior?