11

Background

I have a Visualforce page on which I am rendering a lot of fields using a Dynamic Component (which is an Output Panel that contains all of the fields). The fields are added based on a selection of options in a multipicklist, and each time a selection is made in the multipicklist, the page is re-rendered from the server side to display the new selection of fields. Some of these fields need to be required.

Issue

Whenever a field is required on the page and the selection is changed in the mutipicklist that controls which fields are rendered, Visualforce throws an error that the field has not been filled in, even if it is to be removed based on the selection. This prevents the field from being removed, and the fields on the page don't get updated because of that Visualforce error.

Idea

So, I had the idea to create my own required functionality using jQuery and JavaScript to validate on the client side with a validation method in Apex on the server side.

Question

Is there a better way to do this? If not, how can I duplicate Visualforce's "required" functionality (displaying a red bar next to each of the required fields).

For Reference

Here's a portion of my current Visualforce Page. Products is a multipicklist; whenever the Command Button is clicked, the fields are rerendered.

<apex:pageblocksection columns="1" title="Products" id="ProductSelection" >
    <apex:inputfield value="{!Order.Product__c}" id="Products" />
    <apex:commandbutton value="Select Product" action="{!SelectProduct}" />
</apex:pageblocksection>

<apex:dynamicComponent componentValue="{!SectionsAndFields}" />

The SelectProduct method is extremely simple. It's just a method that does nothing and returns nothing. Mainly it's used to just perform a round trip to the server to rerender the SectionsAndFields property. For a code sample of the SectionsAndFields property, check out this answer from another question.

public void SelectProduct() { }

4 Answers 4

4

Looks like you're after a partial form submission?

Try wrapping the piece you want (both the multiplicklist and the onchange or whatever you have there) in <apex:actionRegion>. As long as the region won't cover any blank mandatory field you should be fine.

The part you rerender can be outside of said region.

<apex:page standardController="Opportunity">
<apex:form>
    <apex:pageBlock title="Master">
        <apex:pageBlockSection title="So many required fields...">
            <apex:inputField value="{!Opportunity.Name}" />
            <apex:inputField value="{!Opportunity.AccountId}" />
            <apex:inputField value="{!Opportunity.StageName}" />
            <apex:inputField value="{!Opportunity.CloseDate}" />
        </apex:pageBlockSection>
        <apex:pageBlockSection title="lets ignore them ;)">
            <apex:actionRegion>
                <apex:inputField label="Type something" value="{!Opportunity.NextStep}">
                    <apex:actionSupport event="onchange" rerender="output" />
                </apex:inputField>
            </apex:actionRegion>
        </apex:pageBlockSection>
    </apex:pageBlock>
    <apex:pageBlock title="Slave: {!Opportunity.NextStep}" id="output">
        <p>This should work even though we're in same form and all required fields will be blank</p>
        <apex:iframe src="/search/SearchResults?sbstr={!URLENCODE(Opportunity.NextStep)}&isdtp=vw" 
            rendered="{!NOT(ISBLANK(Opportunity.NextStep))}" height="500px" />
    </apex:pageBlock>
</apex:form>
</apex:page>

form submission results


Edit after code sample was provided...

If SelectProduct() is used only as a round-trip then you truly don't need it. It's perfectly fine to have an actionSupport (like I did) or commandButton that doesn't have "action" param...

What happens when you try this?

<apex:pageblocksection columns="1" title="Products" id="ProductSelection" >
    <apex:actionRegion>
        <apex:inputfield value="{!Order.Product__c}" id="Products" />
        <apex:commandbutton value="Select Product" rerender="output" />
    </apex:actionRegion>
</apex:pageblocksection>

<apex:outputPanel id="output">
    <apex:dynamicComponent componentValue="{!SectionsAndFields}" />
</apex:outputPanel>
8
  • Great explanation of how an Action Region might work! Unfortunately, I don't think it will work in my situation because I need to do a complete form resubmission to get the list of fields to display. Also, I'm not using Ajax or Action Supports or Action Functions.
    – Matt K
    Dec 12, 2013 at 21:07
  • Either you do need "complete form resubmission" (incl. the mandatory fields) or you don't ;) Consider posting some bits of your form + screenshots? I haven't tried with buttons in action region (everytime it had to be "smart search as you type or as soon as you leave the field" kind of stuff) but it should work anyway I think. Or maybe you should try with multiple forms on 1 page?
    – eyescream
    Dec 12, 2013 at 21:13
  • Here's a portion of the code I'm working with.
    – Matt K
    Dec 12, 2013 at 21:25
  • Bah! That's the "output" part. What in this part (if any) needs to be resubmitted when you change the "config" part? Show us the multipicklist etc ;)
    – eyescream
    Dec 12, 2013 at 21:36
  • I updated my question with a code sample.
    – Matt K
    Dec 12, 2013 at 22:08
3

I use a pattern like this to render conditionally "required" fields:

<apex:pageBlockSectionItem >
    <apex:outputLabel id="someId" value="{!$ObjectType.MyObject.fields[someField].Label}" />
    <apex:outputpanel layout="block" styleClass="requiredInput">
        <apex:outputpanel id="opportunity1Required" layout="block" styleClass="requiredBlock" />
        <apex:inputField value="{!MyObj[someField]}" />
    </apex:outputPanel>
</apex:pageBlockSectionItem>

You can actually hide and show these without a roundtrip to the server:

function toggleRequired(targetId, shouldDisplay)
{
    var targetInputCell = $j('.requiredBlock[id*="' + targetId + '"]').parent().parent();
    targetInputCell.toggle(shouldDisplay);
    targetInputCell.prev().toggle(shouldDisplay);
}

Might need some slight tweaks to implement! You certainly don't need jQuery for this, but I added listeners so I used it because I'm lazy and don't want to reinvent the compatibility wheel.

1
  • Great idea! I did try this in the beginning, but I was forced to render the fields using a Dynamic Component for a variety of reasons (the fields span multiple objects and require different expressions for their value based on some settings).
    – Matt K
    Dec 12, 2013 at 21:11
2

Page

<apex:page extensions="DynamicVFCtrl" standardController="Account">
     <apex:form >
        <apex:dynamicComponent componentValue="{!DynamicForm}"/>
    </apex:form>
</apex:page>

Controller

public class DynamicVFCtrl { 

    public DynamicVFCtrl(ApexPages.StandardController controller) {

    }

    public Component.Apex.PageBlock getDynamicForm() 
    {
        //Creating a PageBlock 
        Component.Apex.PageBlock dynPageBlock = new Component.Apex.PageBlock(); 

        //Creating a PageBlockSection
        Component.Apex.PageBlockSection dynPageBlockSection = new Component.Apex.PageBlockSection();
        dynPageBlockSection.title = 'Create';
        dynPageBlockSection.columns = 2; 
        dynPageBlock.childComponents.add(dynPageBlockSection); 

        for( String field : getSObjectUpdatableFields('Account'))
        { 
            //Creating a InputField
            Component.Apex.InputField theNameField = new Component.Apex.InputField();
            theNameField.expressions.value = '{!Account.'+field+'}';
            theNameField.id = 'the'+field+'Name'; 
            dynPageBlockSection.childComponents.add(theNameField);

        }

        // Create a button to submit the form
        Component.Apex.CommandButton saveButton = new Component.Apex.CommandButton();
        saveButton.value = 'Save';
        saveButton.expressions.action = '{!Save}';
        dynPageBlock.childComponents.add(saveButton); 

        return dynPageBlock;
    }

    public static List<String> getSObjectUpdatableFields(String sObjectName)
    {
        List<String> str = new List<String>();
        SObjectType sObjectType = Schema.getGlobalDescribe().get(sObjectName);
        Map<String,Schema.SObjectField> fields = sObjectType.getDescribe().fields.getMap();

        for(Schema.SObjectField obj : fields.values()) 
        {    
            Schema.DescribeFieldResult fieldType = obj.getDescribe();
            if( fieldType.isAccessible() && fieldType.isUpdateable() &&  !fieldType.isNillable()  )
                str.add(fieldType.getName());
        } 
        str.sort();

        return str; 
    }
}    

enter image description here

1
  • 1
    Scorp, try using the binary code button in the answer editor to have your visualforce code show up. Oct 1, 2014 at 11:34
0

To fix this issue, I duplicated Visualforce's required functionality using JavaScript and jQuery on the client side, I'm also planning on adding server side validation in apex.

Each <apex:inputfield> that is required is rendered with the .required-input class. The reason for this is to easily differentiate in jQuery between required fields and regular fields.

Here's what I ended up with:

<script type="text/javascript">

    var j$ = jQuery.noConflict();
    j$(document).ready(function(){init();});

       function init() {
           // get the elements designated as required
        var elements = j$("input.input-required, select.input-required, textarea.input-required");

        // for each element, wrap it in a requiredBlock div and add the requiredInput class 
        elements.each(function(idx, el) {
            if(j$(el).parent().is("td")) { j$(el).wrap("<div class='requiredInput'></div>").parent().prepend("<div class='requiredBlock'></div>"); }
            else { j$(el).parent().prepend("<div class='requiredBlock'></div>").wrap("<div class='requiredInput'></div>"); }
        });
    }

    function validate() {
        var InvalidElements = [];

        // get the elements designated as required
        var elements = j$("input.input-required, select.input-required, textarea.input-required");

        // make sure each element has a value
        elements.each(function(idx, el) {
            if(el.value == '' || el.value == null) { InvalidElements.push(el); }
        });

        j$(InvalidElements).parents(".requiredInput").append("<span class='errorMsg'>&nbsp;<strong>Error:</strong> You must enter a value</span>").find("input, select, textarea").addClass("error");  

        if(InvalidElements.length > 0) { return false; }
           else { return true; }
    }
</script>

The validate function is called when the save button is clicked. Additionally, all input fields are rendered dynamically using an <apex:dynamiccomponent>.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .