Saturday, 15 November 2014

Visualforce, Required Fields and HTML5

A few weeks ago I encountered some unexpected behaviour around action regions and required fields which after some investigation turned out to be a consequence of using the HTML5 doctype. In this post I’ll present an example of the use of an action region in conjunction with required fields and show how it is impacted by HTML5.

Regular Form

As a very simple example, I have a page that allows the user to enter the name of an account and some additional information in a table.  The user can click the ‘Add Row’ button to add a new row to the additional information:

Screen Shot 2014 09 27 at 08 07 03

The markup for this page is a regular apex:form (the controller isn’t relevant to this post, but is available in the Resources section at the end of this post):

<apex:page controller="RequiredCtrl" tabstyle="Account">
  <apex:pageMessages id="msgs"/>
  <apex:form>
    <apex:pageBlock mode="maindetail">
      <apex:pageBlockButtons location="top">
        <apex:commandButton value="Save" action="{!save}" />
      </apex:pageBlockButtons>
      <apex:pageBlockSection title="Account Information">
        <apex:pageBlockSectionItem>
          <apex:outputlabel value="Name"/>
          <apex:inputfield value="{!Acc.Name}" />
        </apex:pageBlockSectionItem>
      </apex:pageBlockSection>
      <apex:pageBlockSection title="Related Information" columns="1">
        <apex:pageBlockSectionItem>
          <apex:commandButton value="Add Row" action="{!addRow}"/>
        </apex:pageBlockSectionItem>
        <apex:pageBlockTable value="{!rows}" var="row">
          <apex:column headerValue="Value 1">
            <apex:inputText value="{!row.val1}" />
          </apex:column>
          <apex:column headerValue="Value 2">
            <apex:inputText value="{!row.val1}" />
          </apex:column>
          <apex:column headerValue="Value 3">
            <apex:inputText value="{!row.val3}" />
          </apex:column>
        </apex:pageBlockTable>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Clicking the 'Add Row’ button causes the post back to fail as there is a required field missing:

Screen Shot 2014 09 27 at 07 51 34

Adding an Action Region

An action region demarcates a limited section of the form to submit back with the post back. Note that this also requires a rerender component to turn the post back into an Ajax request.  By enclosing my table in an output panel containing an action region, the required field no longer has an impact as it is outside the action region (note that I’m also rerendering my page messages component, as without that any errors will be swallowed as detailed in this post):

<apex:outputPanel id="rows">
  <apex:actionRegion>
    <apex:pageBlockSection title="Related Information" columns="1">
      <apex:pageBlockSectionItem>
        <apex:commandButton value="Add Row" action="{!addRow}" rerender="rows,msgs"/>
      </apex:pageBlockSectionItem>
     ...
    </apex:pageBlockSection>
  </apex:actionRegion>
</apex:outputPanel>

I can now click the ‘Add Row’ button and a new row is generated without errors:

Screen Shot 2014 09 27 at 08 00 34

Using the HTML5 Doctype

If I then decide I’d like to take advantage of some HTML5 features and turn on the HTML5 doctype on the page:

<apex:page controller="RequiredCtrl" tabstyle="Account" doctype="html-5.0">
  ...
</apex:page>

Clicking the ‘Add Row’ button suddenly fails again, but with a different style of error decorator:

Screen Shot 2014 09 27 at 08 01 32

Digging into the rendered HTML shows that an HTML5 specific attribute has been generated:

<input id="..." required="required" size="20" type="text" value="">

this attribute is handled directly by the browser, which obviously has no idea that there is a bunch of JavaScript lying in wait to trap the post back and turn it into an Ajax request, so it just checks the input element and blocks the post back if it is blank.

immediate=“true"

 The same behaviour can be seen when the immediate attribute is set to true for a command button, action function etc., for exactly the same reasons described above.

Workarounds

  • The easiest workaround, if you aren’t relying on any HTML5 features, is to turn off the HTML5 doctype.
  • If you need HTML5 features, make the field non-required by specifying the required attribute as false in your Visualforce markup if you can (you can’t do this with standard object names unfortunately)
  • Manage validation and error messages yourself - see the Field Level Error Messages … links in the Resources section.
  • If neither of the above are suitable, use JavaScript to remove the required attribute from the rendered HTML. This is pretty fragile though, as it requires your JavaScript to know which fields are part of an action region and which aren’t.

Resources

11 comments:

  1. Hi Bob ,
    I am struggling to understand the difference between ActionRegion and reRender can you please explain in detail.

    ReplyDelete
  2. Hi Bob,

    the easiest way to complete turn-off HTML5 validation is to add 'html-formnovalidate="true"' as apex:form attribute.

    this way, you would have the rest of HTML5 features, minus the validation.

    if you want to disable HTML5 validation only in a specific button is clicked, you can add the same attributte int "Add Row" apex:commandButton. this will not trigger HTML5 validation when clicked.

    Regards,
    Aldrin

    ReplyDelete
  3. Hi Bob,

    the easiest way to completely turn-off HTML5 validation is to add 'html-formnovalidate="true"' as apex:form attribute.

    this way, you would have the rest of HTML5 features, minus the validation.

    if you want to disable HTML5 validation only in a specific button, you can add the same attributte in the button, i.e. in your sample's case, in "Add Row" apex:commandButton. HTML5 validation will not trigger when that button is clicked.

    Regards,
    Aldrin

    P.S: realized that my comment above is messed-up...

    ReplyDelete
  4. Hi Bob,

    the easiest way to completely turn-off HTML5 validation is to add 'html-novalidate="true"' as apex:form attribute.

    this way, you would have the rest of HTML5 features, minus the validation.

    if you want to disable HTML5 validation only in a specific button, you can add the corresponding attribute in the button, i.e. in your sample's case, in "Add Row" apex:commandButton. HTML5 validation will not trigger when that button is clicked.

    Regards,
    Aldrin

    P.S: realized that my comment above is messed-up...

    ReplyDelete
  5. Hey Bob,

    Another way around this that I've just used - I separated different sections of the page into different forms (I had this issue on a modal).

    Andy

    ReplyDelete
  6. Now for your next trick...

    Put the red line on 1 or more fields in the pageblocktable..

    By the way when I published under my google name after logging into gmail and being redirected to this site, the browser in the bottom left hand corner was trying to connect to alexgorbatchev.com.

    Not sure if Bob´s site or my google account has been compromised.

    ReplyDelete
  7. Also when I publish there are attempted connections to alexgorbatchev.com

    ReplyDelete
    Replies
    1. Alex Gorbatchev wrote the syntax highlighter that formats the code snippets - this blog includes JavaScript from his site. You can read more about how this works at : http://bobbuzzard.blogspot.co.uk/2014/01/syntax-highlighting-in-knowledge.html

      Delete
  8. This comment has been removed by the author.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Hi Bob, I was able to use the HTML 5 required syntax on a force.com site and worked great on desktop. Leaving the fields blank produced the error message on the fields when clicking on the submit button. However, when I try it on a mobile device, the blank fields are not recognized as being blank and the form gets processed when I press the submit button.

    Do you know what I'm missing in my CSS or maybe APEX when doing this on mobile? Thanks!!

    ReplyDelete