function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
KevinPKevinP 

The curious case of the insubordinate CommandButton

Good day Ladies and Gents.

I lay before you the fruit of many days of frustration, labor and tears, begging for your help.

There are a few moving parts here, so let me introduce them one by one, with nicknames.

TheVisualForcePage - A humble visualforce page, attached to standard controller User, with a custom extension.

TheVFExtensionCtrl - A braggadocious apex class, who wants the world to know how awesome it is(n't).

TheVFComponentOfDoom - A visualforce component with a custom Controller that exposes two very insubordinate Command buttons. In our case, this component exists to provide a re-usable "delete" button, associated logic and general frustration.

TheVFComponentCtrl - An apex class whose sole purpose in life is to delete an F'ing record. Like the fat kid at primary school sports day, it's main issue is that nobody calls on it. (and therein lies our problem)

To set the stage with these moving pieces, we should consider that the Visualforce page displays, depending on mode, a list of either makes, models or years of cars owned by the given user. Because in my fictional world, users may own multiple Ferrari F12 Berlinetta's (different colors of course.) We can safely assume that if one were to navigate to Farrari, and then Berlinetta, we would see at least one record displayed on our VF page. (2013 of course). These records are displayed in a custom list view format created by using an Apex:Repeat tag.

It's here where we discover our first problem. The list view has, as it's last column a series of "actions" like: "Sell", "Delete" (dear god, who would ever delete a Farrari f12???) and edit. Sell and Delete are command buttons exposed via our handy components. To simply this issue, lets pretend we only have a delete button.

Now, to the untrained, unsuspecting eye a visualforce component, with it's own controller invoked on a page during an Apex:Repeat loop doesn't sound all that complicated. Sure there's the issue of passing in an ID to the logic of the component controller, but that's time-tested, mother approved. Indeed, I thought I was, well, done with this until ...

As it turns out, pressing the delete button has a very curious set of consequences in this setup, consequences I can not fully explain, nor fix.

When you click on the delete command button, the page refreshes (yay, i want that!) However:

Detailed logging shows that the master vfpage's associated controller extension's constructor is executed. Without error
That the component's deleteThisCar method is Never invoked.
Visually, the page state has been lost. What do I mean? I mean that the page that was displaying years of Ferrari F12 Berlinetta's is now just blank, showing only the headers of the listview.

Not to be outdone by an overgrown java app, I've tried the following approaches:

Updating my code to use apex:ActionRegion tags around the components
Tried every damn combination of reRender on the command buttons. Curiously, this had the effect of not reloading the page, and not calling my apex method.
I said, F-IT loudly and refactored the code to not use a component -- invoking an action method directly on the master controller, but this also failed! causing the page to reload, without my action method being invoked
I have slept on the problem. (no joke, this usually works for me. I wake up with the answer.)
I have asked Co-workers to look at it. They suggested the actionRegion bit.
I tried giving up on my trusty commandButtons attempting to use a standard input button with an ActionFunction -- Curiously, this invokes the constructor of my component controller, but not the actual delete function.

Suffice it to say, that overgrown java app is raining on my day.

This feels like somehow the wrong form is being submitted, which is distinctly maddening because i've also rewritten the master vf page such that it had 2 forms (search in header, main page form) and 5 forms (Search in header, 1 form per "mode") Neither has worked.

I realize that it'd be hypocritical in the extreme if I posted this question without some kind of code attached, so here's the component and it's extension. The VF page itself is quite lengthy and I've not finished "sanitizing" it for public consumption.
 
<apex:component controller="ACE_DeleteCarCmpCtrl" allowDML="true">
<apex:attribute name="tv"
    description="ID of the Car model and year to display controls for."
    type="Id"
    required="false"
    assignTo="{!CarVersionId}"
/>
<apex:attribute name="t"
    description="ID of the Car model to display controls for."
    type="ACE_Track__c"
    required="false"
    assignTo="{!Car}"
/>


    <apex:outputPanel layout="block" id="theoutputpanel">
    <apex:actionRegion >
    <!-- <apex:actionFunction name="sayHello" action="{!deleteTrackOrVersion}" rerender="TrackVersionsForSelectedTrack" /> -->
        <apex:commandButton action="{!deleteCarOrYear}"
            value="Delete Car"
            rendered="{!IF(ISNULL(Car), false , true)}"
            styleClass="btn btn-sm btn-default"
            />
        <!-- <input type="button" class="submit" onclick="sayHello"/> -->
        <apex:commandButton action="{!deleteCarOrYear}"
            value="Delete Car Version"
            styleClass="btn btn-sm btn-default"
            rendered="{!IF(ISNULL(CarVersionId), false , true)}"
            rerender="nothing"
            />
    </apex:actionRegion>
    </apex:outputPanel>
</apex:component>
and the controller for it:
public with sharing class ACE_DeleteCarCmpCtrl {

Public ACE_Car_Version__c carVersion {get; set;}
Public Id carVersionId  {get; set { carVersionId = value; }}
Public ACE_Car__c car {get; set;}

public ACE_DeleteCarCmpCtrl() {
    system.debug('$$$$$$$$$$$$$$ : ' + ApexPages.currentPage().getParameters());
}

public PageReference deleteTrackOrVersion() {
    system.debug('************* : ' + ApexPages.currentPage().getParameters());
    try {
        if (car != null && carVersion != null) {
            throw new ACE_contentManagementLib.ContentManagementException('Both car and carVersion cannot be populated when invoking this component');
        } else if (carVersion == null && car == null) {
            throw new ACE_contentManagementLib.ContentManagementException('Both car and carVersion cannot be null when invoking this component');
        } else if (carVersion != null) {
            ACE_ContentManagementLib.deletecarVersion(carVersionId);
        } else if (car != null) {
            ACE_ContentManagementLib.deleteTrack(track);
        }
    } catch (ACE_ContentManagementLib.ContentManagementException e) {
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, e.getMessage()));
    }
    //return null;
    //Also tried null above. no joy.
    PageReference pageRef = new PageReference('/AwesomePage?id=' + UserInfo.getUserId() );
    system.debug('$$$$$$$$$$$$$$ : ' + ApexPages.currentPage().getParameters());
    pageRef.setRedirect(true);
    return pageRef;
}
}


 
Gaurav KheterpalGaurav Kheterpal
P.S - > As a moderator on the Developer Forums, I really wish there were more posts so beautifully worded as this one. :)

Interesting problem, my first instinct (without reading what all you have already tried) would have been to recommend using the ActionRegion but it seems that hasn't really yielded the result you are looking for.

Hoping to find some answers on this thread.
 
MJ Kahn / OpFocusMJ Kahn / OpFocus
I agree - a beautifully worded post! A real treat to read, and between the wording, the humor, and the technical problem, I was intrigued enough to spend some time coming up with a simplified example of what I think KevinP was trying to do.

In my example, I created a VF page named AccountAndContacts that lists all of the Contacts for a given Account. It uses the standard Account controller and an extension that returns a list of Contacts. The page displays a pageBlockTable of Contacts, and for each Contact, includes an apex:column that includes a custom component that contains a Delete button. That custom component accepts a Contact Id as an argument, and displays a commandButton that calls a delete action method that's implemented by the custom component's controller.

The code is below, and after a little trial and error, it works. The issue for me wasn't getting my custom component's controller's delete action to be called. It was getting the action to cause the page to be rerendered. I tried having the action method return null, and also tried returning an explicit reference to the VF page, with varying permutations of a rerender attribute on the commandButton. I finally had the delete action return the pageReference with setRedirect set to true. That caused the page to re-load, so it would display the list of Contacts without the deleted Contact.

KevinP, it was a little tough to determine exactly what the problem with your code was because you didn't include the code for the page or its controller. Also, without a simplified example or one that I could reproduce without recreating your schema, I couldn't reproduce your problem exactly. But I hope the following is a helpful guide to you.

VF page: AccountAndContacts
<apex:page standardController="Account" extensions="AccountAndContactsController">
	
	<apex:form >
		<apex:sectionHeader title="Contacts for {!Account.Name}" />
		
		<apex:pageBlock title="Contacts">
			<apex:pageBlockSection columns="1">
				<apex:pageBlockTable value="{!lstContacts}" var="c">
					<apex:column headerValue="Action">
						<c:AccountAndContactsDelete cId="{!c.Id}"/>
					</apex:column>
					<apex:column value="{!c.FirstName}" />
					<apex:column value="{!c.LastName}" />
					<apex:column value="{!c.Email}" />
				</apex:pageBlockTable>
			</apex:pageBlockSection>
		</apex:pageBlock>
	</apex:form>

</apex:page>

VF page controller extension: AccountAndContactsController
public with sharing class AccountAndContactsController {

	public List<Contact> lstContacts {get; private set;}

	public AccountAndContactsController(ApexPages.StandardController std) {
		lstContacts =
		  [select id, FirstName, LastName, Email from Contact where AccountId=:std.getId()];
	} 
}

VF component: AccountAndContactsDelete
<apex:component controller="AccountAndContactsDeleteController" allowDML="true">

	<apex:attribute name="cId" description="Id of a Contact to delete" type="Id" required="true" assignTo="{!contactId}" />

	<apex:actionRegion >
		<apex:commandButton action="{!deleteContact}" value="Delete" rerender="me"/>
	</apex:actionRegion>

</apex:component>

VF component controller: AccountAndContactsDeleteController
public with sharing class AccountAndContactsDeleteController {

	public Id contactId {get; set;}
	
	public PageReference deleteContact() {
		Contact c = [select id, AccountId from Contact where id=:contactId];
		delete c;
		
		PageReference pgRef = new PageReference('/apex/AccountAndContacts?id=' + c.AccountId);
		pgRef.setRedirect(true);
		return pgRef;
	}
}




 
RustyboyRustyboy
Did anyone solve this problem, because I am experiencing the very same?