4

Originally I wanted to attempt using the interface notation as an added layer of abstraction for this project for the time being I'm going with what I believed to be a simpler solution, unfortunately I'm having issues.

The problem is my getter/setter variables are not working properly (at least as I understand them to work). Here's the code breakdown:

Visualforce Page:

<c:Gauge widget-type="Number" report="Report Name" data-key="T!T" />

Visualforce Component:

<apex:component controller="GaugeController">

    <!-- Define Component Attributes -->
    <apex:attribute name="widget-type" assignTo="{!type}" description="Type of gauge to be used" type="String" required="true"/>
    <apex:attribute name="report" assignTo="{!reportName}" description="Name report data to be used" type="String" required="true"/>
    <apex:attribute name="data-key" assignTo="{!key}"  description="Field or column key used from the DataDetail" type="String" required="true"/>

    <!-- Define Component Body Variables -->
    <apex:variable var="result" value="{!reportName}"/>

    <!-- Define Component -->
    <apex:outputPanel>
        <apex:outputText>{!result}</apex:outputText>
    </apex:outputPanel>

    <apex:componentBody />

</apex:component>

Component Controller:

public class GaugeController extends Widget{

    public String key { get; set; }

    /* Construtor */
    public GaugeController() {
        System.debug(reportName);  //returns NULL
    }

}

Abstract Base Class:

public abstract class Widget implements Data{

    //========= SETUP GETTERS/SETTERS ============//
    public String title { get; set; }
    public String type { get; set; }
    public String reportName { get; set; }

    public Widget() {
        System.debug(reportName);  // returns NULL
    }
}

I don't understand why my System.debugs show NULL values yet the page gives me the String result I passed as expected. I think I need to be enlightened a little. Any and all help is much appreciated :)

1 Answer 1

3

The use of an abstract base class in itself should not cause issue. I think the problem here is bigger.

Visualforce Component getters / setters / constructors have an unusual and extremely lax contract with each other. I think the component controller code is subject to all the following ambiguities:

  • setter order is non-deterministic
  • getter order is non-deterministic
  • construction order is non-deterministic

Unlike Visualforce Pages, which offer an action attribute to give you a second bite at the cherry, you are basically out of luck relying on any kind of ordering with Visualforce Components.

The only thing that is guaranteed is that a property is set before you get it.

Reliability can be achieved by combining all the input parameters into one "super attribute" which is manipulated in a setter, and overwrites itself with the end result. Falling back to the Object primitive reconciles the restraints in the visualforce output layer, while allowing the Apex code to still execute as needed.

I know it sounds far fetched. And I'd be very grateful if anyone can demonstrate reliability some other way. Below, you can see how your component might be re-written to the above ends :-)


Visualforce Page

<c:Gauge TypeReportKey="Number,Name,T!T" />

Visualforce Component

<apex:component controller="GaugeController">

    <!-- Define Component Attributes -->
    <apex:attribute name="TypeReportKey" assignTo="{!Params}" description="Type of gauge to be used, Name report data to be used, Field or column key used from the DataDetail" type="Object" required="true" />

    <!-- Define Component Body Variables -->
    <apex:variable var="result" value="{!Params}"/>

    <!-- Define Component -->
    <apex:outputPanel>
        <apex:outputText>{!result}</apex:outputText>
    </apex:outputPanel>

    <apex:componentBody />

</apex:component>

Component Controller

public class GaugeController {

    public Object Params;

    public void setParams(Object value) {
        String type   = ((String)value).split(',')[0];
        String report = ((String)value).split(',')[1];
        String key    = ((String)value).split(',')[2];
        this.Params = someWidgetFunction(type, report, key);
    }

    public Object getParams() {
        return this.Params;
    }
}

(The insanity is contained within the setter... someWidgetFunction is where your code goes)

6
  • What does that mean for me? How should I be doing this? Essentially passing a string that I will use with Reporting API to provide data for a component to render a visual. Commented Dec 10, 2015 at 23:53
  • So I can tell it to assign to a variable and it could not assign it because of its agreement? Commented Dec 10, 2015 at 23:54
  • 2
    We can, however, guarantee that constructors will be called before setters, thus any value you debug in a constructor for that same class will be null.
    – sfdcfox
    Commented Dec 11, 2015 at 0:08
  • @sfdcfox - So from the constructor (of both the base and child class), I cannot even retrieve the value that was sent by the component? Or I can retrieve it but it won't be SET but perhaps I could set it myself? Commented Dec 11, 2015 at 0:15
  • @Xtremefaith Yes, you can set it yourself in your constructor, but since it's assigned a value in Visualforce, it'll be clobbered during the setter phase. In other words, if you wanted to know what the value was, you'd do so in a setter function, as you stated in your answer.
    – sfdcfox
    Commented Dec 11, 2015 at 0:19

You must log in to answer this question.

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