7

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:

  1. Detailed logging shows that the master vfpage's associated controller extension's constructor is executed. Without error
  2. That the component's deleteThisCar method is Never invoked.
  3. 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:

  1. Updating my code to use apex:ActionRegion tags around the components
  2. 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.
  3. 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
  4. I have slept on the problem. (no joke, this usually works for me. I wake up with the answer.)
  5. I have asked Co-workers to look at it. They suggested the actionRegion bit.
  6. 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="ID"
    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>

The best controller ever:

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

}

Help me Obi-won-stackOverflow, you're my only hope!

  • As it stands, what you have above won't compile, the t attribute is an ACE_Track__c but being assigned to an ACE_Car__c, the deleteCarOrYear method does not exists... there may be more, but I found those and stopped looking. Any chance you could fix up your nice stripped down example so it compiles to aid diagnosis? – Alex Tennant Feb 10 '15 at 12:34
  • I have found with command buttons that if they are NOT initially rendered, rendering them later causes them NOT to work. What I do in that case is to use standard html buttons (hidden as appropriate) and have them call action functions. – Eric Feb 10 '15 at 14:04
  • @alex agh. Sorry. I tried cleaning this up but have been awake fighting it too long. When I'm not on my phone I will do a better edit job. In the meantime you can ignore the track/track versions references – Kevin P Feb 10 '15 at 14:19
  • @eric these buttons are always rendered. Additionally as I stated in the original bit that I've tried firing by an action function to little help. An action function will invoke the component controllers constructor but not the delete method. – Kevin P Feb 10 '15 at 14:21
  • @KevinP - Ok. I just saw this and thought they may not be rendered at some point...rendered="{!IF(ISNULL(CarVersionId), false , true)}" – Eric Feb 10 '15 at 16:59
2

So ladies and gents. This turned out to be a case of premature optimization.

(If you experience an optimization lasting 4 hours please see a qualified technical architect)

Turns out that if you know you're going to have a big list-o-data, and you just prematurely put the transient word in front of it's definition ... well weird a$$ stuff happens.

Moral of the story, don't mark the collection you're going to apex:repeat over as transient unless you want to scratch your eyeballs out and post humorous stack exchange questions in raw desperation as you download half a gallon of bourbon.

  • Yes but your post was one of the best on SFSE yet. I found it a very fun question. As I suspected it was something unique in your org that was not reflected in the code you posted.....Not that I like you went through all that but the resultant question from your misery was a welcome change... – Eric Feb 12 '15 at 22:18
0

That's really long, and I didn't take in all the details but this might help you...

I had a similar situation with wanting to put components inside a repeat, with similar crazy results. I ended up namespacing my component to make all of the ajax calls to the controllers unique. Weird, ugly, but it works.

The component definition was:

<apex:component controller="SortableListController">
<apex:attribute name="uniqueString" assignTo="{!uniqueStringParam}" type="String" description="A string to make action functions inside the component unique. Should be unique for each component used on the same page and valid to go on the end of a JavaScript variable name." required="true"/>

Then my actions functions were like this:

<apex:actionFunction action="{!gotoFirstPage}" name="gotoFirstPageJS{!uniqueString}" reRender="pb" status="pb:sortStatus"/>

So the invocation was:

<span class="linkClass" onclick="gotoFirstPageJS{!uniqueString}()">&lt;&lt;</span>

And to instantiate this component from my VF page, I did this:

<apex:repeat value="{!projects}" var="thisProject" >  
  <c:Sortable_List uniqueString="{!thisProject.id}" 

Make sense? Helpful?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.