Forms Required Resources Missing

Vote:
 

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?

 

#274665
Feb 28, 2022 3:41
Vote:
 

Here are the steps I took to get forms with js support and the builtin css working. To summarize:

  1. Provide own jQuery, either through CDN or served from your app
  2. Configure Forms.config to tell Forms to use own jQuery
  3. Reference static files from the Forms module (js is omitted and different css served in Edit Context) and
    Use instance methods from a newed-up EPiServer.Forms.Controllers.FormContainerBlockController to generate javascript
  4. Use AntiForgeryToken on every page
  5. Set app's default json serializer to use CamelCase.

1. Configure for own jQuery

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>

2. Create partial views for forms static files

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>
}

3. Use the partial views in your layout

The first goes in the <head> section and the second at the end of <body>.

4. Add @Html.AntiForgeryToken() to your layout

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.

5. Set JsonNamingPolicy to CamelCase

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;
    });
  }
#275575
Edited, Mar 03, 2022 5:05
Vote:
 

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! 

#275591
Mar 03, 2022 14:28
Camilla - Mar 03, 2022 15:06
Note: Im running 12.3.1, but I tried it on a downgraded website on episerver.cms 12.1 and then resources are loaded as expected.
Vote:
 

Oh, I think you're right. I probably should have just waited for 12.4 to be released. Interesting that you got it to work on 12.2.

#275598
Edited, Mar 03, 2022 15:29
Camilla - Mar 03, 2022 15:44
Perhaps just saved by old dlls :P but def. worked in 12.1 and now in 12.3.1 it doesnt work.
Vote:
 

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 

#275644
Edited, Mar 04, 2022 7:46
Vote:
 

Thanks, that fixed it for me, too!

#275677
Mar 04, 2022 17:04
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.