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
pcalpcal 

Communication between controllers on the same page?

Is there any way for a page controller and custom component controller on a page to find each other and talk to one another? 

For complex pages, I'd like to be able to encapsulate different areas of functionality between different components, but still have the ability for the components to pass messages to each other.

For example, consider a page that has a query builder form and a result pane.  I'd like to have one component/controller handle a complex UI mechanism for building up a query and another component/controller that handles the retrieval and display of results.

I understand most of how to do this, but I don't see how I can get the components to talk to each other.  When the user clicks 'Run Query!' on the query component, I need that controller to take all of the UI inputs, assemble a query string, and then pass that result over to the result component and perform a refresh.

How should I go about achieving something like this in VisualForce?  I realize I could do it with one giant page controller that handles everything, but I'm hoping to get a more elegant separation of concerns here (my actual case is a bit more complicated than I describe here).

Thanks much in advance.

-Patrick
pcalpcal
Just to clarify: when I say 'component' here I mean 'custom component,'
dchasmandchasman
Components cannot currently be manipulated in apex code (something on the roadmap) but you can have a set of components share a common model object injected into each from an extension/controller that the page is bound to. This pattern - dependency injection or inversion of control - is pretty common and powerful (a common example is a data source or data provider).

I typically create an apex class to represent the shared state (let's call it SharedState for this example), add a required attribute of type SharedState to the set of components that need to communicate with each other Then I creata a shared state ("data provider") controller extension that has manages the single shared instance of SharedState that I can mix into any controller.

/apex/dataproviderdemo:
<apex:page standardController="Account" extensions="DataProvider" showHeader="false">
    <apex:form id="theForm">
        <c:sharedStateDemoA state="{!sharedState}" rerender="theForm"/><br/>
        <c:sharedStateDemoB state="{!sharedState}" rerender="theForm"/><br/><br/>
        
        <apex:commandLink value="Increment counter from the Page" action="{!sharedState.incrementCounter}" rerender="theForm"/>&nbsp;   
        <apex:commandLink value="Decrement counter from the Page" action="{!sharedState.decrementCounter}" rerender="theForm"/>    
    </apex:form>
</apex:page>

 
/apexcomponent/sharedStateDemoA:
<apex:component>
    <apex:attribute name="state" type="SharedState" description="TODO: Describe me"/>
    <apex:attribute name="rerender" type="String" description="TODO: Describe me"/>

    Some shared state in SharedStateDemoA [{!$Component.this}]: {!state.counter}
    <apex:commandLink value="Increment Counter" action="{!state.incrementCounter}" rerender="{!rerender}"/>
</apex:component>

 
/apexcomponent/sharedStateDemoB:
<apex:component>
    <apex:attribute name="state" type="SharedState" description="TODO: Describe me"/>
    <apex:attribute name="rerender" type="String" description="TODO: Describe me"/>

    Some shared state in SharedStateDemoB [{!$Component.this}]: {!state.counter}
    <apex:commandLink value="Decrement Counter" action="{!state.decrementCounter}" rerender="{!rerender}"/>
</apex:component>

 
Code:
public class SharedState {
    public Integer getCounter() {
        return counter;
    }
    
    public void incrementCounter() {
        counter++;
    }

    public void decrementCounter() {
        counter--;
    }
    
    private Integer counter = 0;
}

 
Code:
public class DataProvider {
    public SharedState getSharedState() { return sharedState; }

    public DataProvider(DataProviderTest controller) {
    }

    public DataProvider() {
    }

    public DataProvider(Apexpages.StandardController controller) {
    }


    private SharedState sharedState = new SharedState();
}

 







Message Edited by dchasman on 08-02-2008 10:14 AM
Mark YoungMark Young
To further expand on Doug's post, explictly passing messages between the components can be done by using a
listener pattern in this shared class.
 
Shared class contains a list of listeners:
Code:
List<IListener> listeners = new List<IListener>();
public void addListener(IListener listener)
{
    // Check for uniqueness first
    listeners.add(listener);
}

public interface IListener
{
    void OnNext();
}

 
And the component registers itself as a listener on the shared class:
Code:
public class Component_Controller
{
  /** Setter for the shared class attribute */
  public void setSharedClass(SharedClass shared)
  {
    shared.addListener(new SharedListener());
  }

  protected class SharedListener implements SharedClass.IListener
  {
    /** Override of interface */
    public void OnNext()
    { 
      // Do logic here...
    }
  }
}

 
The shared component can then fire off events that can communicate between page / component:
Code:
void nextButtonAction()
{
  for (IListener l : listeners)
  {
    l.OnNext();
  }
}

 
pcalpcal
Thanks very much to you both for your fast response!

Right, I was hoping that there was some kind of injection mechanism - for some reason it just never occurred to me to pass my own objects in via attributes.  I implemented this in my code yesterday - separate component controllers listening to each other, wired up by the page controller - everything works great.

The only thing I might wish for is the ability to actually inject the controller instance that a given component uses.  As it is, my <apex:components> skip the controller attribute entirely and instead just get their 'controller' via an 'injectedController' attribute.  Not a big deal except that I have to qualify references everywhere in the component definition ({!injectedController.foo})...

Anyway, that's a quibble - things are working very nicely now.  Thanks for setting me on the right track.

Cheers,
-p

Mark YoungMark Young
You can use a <apex : variable > to minimize the references (and skip a few getter method calls)
i.e.
Code:
<apex:variable var="foo" value="{!injectedController.foo}" />

 
leads to being able to use {!foo.bar}