Button to run a flow fails when I press it twice consecutively - salesforce

Below is my controller
public class FlowController {
public String flowResult {get; set;}
Map<String, Object> inputs = new Map<String, Object>();
public Integer ICP_Count {get; set;}
public String dbHtml {get; set;}
Flow.Interview.Flow_ICP_Component_Update_Workflow myFlow =
new Flow.Interview.Flow_ICP_Component_Update_Workflow (inputs);
public void start() {
myFlow.start();
ICP_Count = Integer.valueOf(myFlow.getVariableValue('ICP_Count'));
flowResult = 'Flow Completed';
}
}
Visualforce page
<apex:page controller="FlowController">
<apex:pageBlock title="ICP Component Flows">
<apex:form>
<apex:commandButton action="{!start}" value="UpdateComponentWorkflow" reRender="text"/>
<br></br>
<apex:outputLabel id="text">Total ICPs Processed = {!ICP_Count}</apex:outputLabel>
<br></br>
<apex:outputLabel id="text3">FlowResult = {!flowResult}</apex:outputLabel>
</apex:form>
</apex:pageBlock>
</apex:page>
When I click the "UpdateComponentWorkflow" button for the first time the flow seems to run fine and updates the "Total ICPs Processed" field. But, it doesn't update the "FlowResult". And when I click the button again I get the following error
Interview already started.
Error is in expression '{!start}' in component <apex:commandButton> in page flow_jobs: Class.Flow.Interview.start: line 51, column 1
Class.FlowController.start: line 15, column 1
An unexpected error has occurred. Your solution provider has been notified. (Flow)
To get the button working again I have to refresh the page. My assumption is the flow (autolaunched) is not ending properly or something to do with the lifecycle of the VF page. I would like some guidance on this.
Edit: I thought it would be helpful if I showed the flow diagram

The problem is that you are creating a singleton instance of the Flow which you start the first time you click the button. Then, when you click the button again, you are calling start() on the same instance of the Flow.
The way to resolve this would be to instantiate the Flow every time you click the button, like so:
public class FlowController {
Flow.Interview.Flow_ICP_Component_Update_Workflow myFlow;
Map<String, Object> inputs = new Map<String, Object>();
public void start() {
myFlow = new Flow.Interview.Flow_ICP_Component_Update_Workflow(inputs);
myFlow.start();
}
}

Related

Why can't I iterate through a list of a Wrapper class in Salesforce Visualforce?

I am trying to iterate through a list of records inside a wrapper class and show them on a Visualforce page. The custom object is called Campaign_Products__c, and the wrapper class is meant to show if the product has been selected by the user to add to a "cart".
Apex Controller code (extraneous bits removed):
public with sharing class CONTROLLER_Store {
...
public List<productOption> cpList { get; set; }
public class productOption {
public Campaign_Product__c product;
public Boolean inCart;
public Integer quantity;
}
...
public CONTROLLER_Store(){
...
List<Campaign> cmpList = getCampaignWithProducts(CampaignId,'');
// method above calls a campaign with a related list of Campaign Product records
if(cmpList.size() > 0){
cmp = cmpList[0];
cpList = new List<productOption>();
for(Campaign_Product__c pro : cmp.Campaign_Products__r){
productOption option = new productOption();
option.product = pro;
option.inCart = false;
option.quantity = 0;
cpList.add(option);
}
} else {
cmp = new Campaign();
CampaignId = null;
cpList = new List<productOption>();
}
....
}
Visualforce page (extraneous bits removed)
<apex:page controller="CONTROLLER_Store" >
<apex:repeat value="{! cpList }" var="option">
{!option.product.Product__r.Name}
<apex:inputCheckbox value="{! option.inCart }"/>
</apex:repeat>
</apex:page>
I get this error when trying to save the visualforce page:
Unknown property 'CONTROLLER_Store.productOption.product'
You need to make the properties in your wrapper visible to VF too. Something like
public class productOption {
public Campaign_Product__c product {get; private set};
public Boolean inCart {get; set};
public Integer quantity {get; set};
}
(assuming product should be readonly in VF). You need these access modifiers or full getter/setter methods.

Visualforce not reading variables below element

Ok so the title is confusing and the question will likely be too as Im new to this whole VF/Apex thing and dont know proper terminology.
The idea is when the user selects one of the options ("other") then the controller will concatenate the value of the textbox (!otherString) into the custom object.
The issuse is I want the input textbox to go below the select menu but if I put it below the menu the user input is no longer readable.
This works:
<apex:inputText value="{!otherString}" id="otherText" />
<apex:selectCheckboxes layout="PageDirection" value="{!other}" id="standardMap" required="true">
<apex:selectOptions value="{!shareTypes}" />
</apex:selectCheckboxes>
Output: "Other: Model" ("Model" being whatever the user typed)
This doesnt work:
<apex:selectCheckboxes layout="PageDirection" value="{!other}" id="standardMap" required="true">
<apex:selectOptions value="{!shareTypes}" />
</apex:selectCheckboxes>
<apex:inputText value="{!otherString}" id="otherText" />
Output: "Other: null" (null being because it couldnt read the textbox)
Relevant Controller Code:
List<String> other = new List<String>{};
public String otherString{get;set;}
public List<String> getOther() {
return other;
}
public void setOther(List<String> other) {
Set<String> mySet = new Set<String>();
mySet.addAll(other);
if (mySet.Contains('Other'))
{
String result = String.join(other, ', ' );
control.Share_Types__c = result + ': ' +otherString;
}
else
{
String result = String.join(other, ', ' );
control.Share_Types__c = result;
}
}
but if I put it below the menu the user input is no longer readable
Your code kind of relies on the order in which getter and setter methods will execute (if you'll enable debug logging or fire up developer console you'll see that the order of get/set statements kind of matches the order in which expressions appear on the page... but there's a chance you'll see something firing more than once!).
Salesforce recommends not relying on the order in which get/set fire and your application shouldn't care if setOther is called once or 10 times (so you shouldn't have complex queries as side-effects in setters for example).
I assume you already have some action method (something that's bound to apex:commandButton or apex:commandLink) - you should move this logic from the setter to that method instead. Do you have our own method or are you using StandardController's {!save} for example?
Some further reading:
https://www.salesforce.com/us/developer/docs/pages/Content/pages_controller_methods.htm (check the warnings with red exclamation marks)
https://salesforce.stackexchange.com/questions/9167/getter-setter-confused (a shameless plug :P)
http://www.tehnrd.com/getter-method-order-and-visualforce-pages/
Edit - you've mentioned you're looking into some submitting without user having to click any button. Check if something like this is close to what you need.
public class Checkboxes{
public List<SelectOption> options {get;private set;}
public List<String> selected {get;set;}
public Boolean otherSelected {get; private set;}
public String otherOption {get;set;}
public transient String output {get; private set;}
public Checkboxes(){
options = new List<SelectOption>{
new SelectOption('Apex', 'Apex'),
new SelectOption('JavaScript', 'JavaScript'),
new SelectOption('Visualforce', 'Visualforce'),
new SelectOption('Other', 'Other')
};
selected = new List<String>();
otherSelected = false;
}
public void skillsChanged(){
try{
Set<String> temp = new Set<String>();
temp.addAll(selected);
otherSelected = temp.contains('Other');
if(otherSelected){
output = 'Other : ' + (String.isBlank(otherOption) ? '<not specified>' : otherOption);
} else {
output = String.join(selected, ', ');
}
System.debug(output);
} catch(Exception e){
ApexPages.addMessages(e);
}
}
}
<apex:page controller="Checkboxes" tabStyle="Account">
<apex:pageMessages />
<apex:form>
<apex:pageblock id="block">
<apex:pageBlockButtons location="top">
<apex:actionStatus id="status">
<apex:facet name="start"><img src="/img/loading.gif" alt="Loading" title="Loading"/></apex:facet>
</apex:actionStatus>
</apex:pageBlockButtons>
<apex:pageBlockSection title="input" columns="1">
<apex:selectCheckboxes label="Your skills" value="{!selected}" required="true">
<apex:selectOptions value="{!options}" />
<apex:actionSupport action="{!skillsChanged}" event="onchange" status="status" rerender="messages, block"/>
</apex:selectCheckboxes>
<apex:inputText label="Please Specify" value="{!otherOption}" rendered="{!otherSelected}">
<apex:actionSupport action="{!skillsChanged}" event="onchange" status="status" rerender="messages, text"/>
</apex:inputText>
</apex:pageBlockSection>
<apex:pageBlockSection title="output">
<apex:outputText value="{!output}" id="text"/>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>

Bug with apex:selectCheckboxes in Spring'13 release

Right now it seems that anything with the <apex:selectCheckboxes> wont work (regardless of version since I did try bumping it back on both the page and controller). See blog entry for full debugging steps:http://salesforcegirl.blogspot.hu/2013/05/bug-with.html
when trying to use a repeat to populate the Lists via maps it fails as well for both <apex:selectCheckboxes> and <apex:selectList>. But if you do it the long way, you can get <apex:selectList> to work at the very least, however not the <apex:selectCheckboxes> (which is what i need).
Here is the code:
public class sfg_testBugWithActionButton {
public String fGrade {get; set;}
public List<SelectOption> soGrade {get; set;}
public String resultString {get; set;}
public sfg_testBugWithActionButton() {
createfilterMap();
resultString = 'on Load of page';
}
public PageReference preformAction() {
system.debug('Grade: ' + fGrade);//this wont be hit unless you use selectList
resultString = 'button action preformed';
return null;
}
private void createfilterMap() {
soGrade = new List<SelectOption>();
soGrade.add(new SelectOption('A', 'A'));
soGrade.add(new SelectOption('B', 'B'));
soGrade.add(new SelectOption('C', 'C'));
}
}
Page:
<apex:page showHeader="true" sidebar="true" controller="sfg_testBugWithActionButton">
<apex:form>
<apex:outputpanel id="mainWrap">
<h2>Grade</h2>
<apex:selectCheckboxes value="{!fGrade}" layout="pageDirection">
<apex:selectOptions value="{!soGrade}" />
</apex:selectCheckboxes>
<apex:commandButton action="{!preformAction}" rerender="renderWrap" value="Submit Action" />
<br />
<apex:outputpanel id="renderWrap">
{!resultString}
</apex:outputpanel>
</apex:outputpanel>
</apex:form>
</apex:page>
<apex:selectCheckboxes> hold multiple values so its value needs to be an array of strings:
public List<String> fGrade {get; set;}
Then you just need to initialize it in the constructor, and the example in the question works:
this.fGrade = new List<String>();

Combine multiple objects within a list

I'm looking to create a single list of records of various sObject types using apex to display to a custom visualforce page. I'm wondering if there is a way to combine multiple objects (case, opportunity, account, etc) within a single list. If not, how do you append the second object to the first in a list using visualforce code? Is there a best practice?
Thanks
So I could use a little more assistance completing this. I have written this:
public class MyController {
public List<ContactAndCase> ContactsAndCases { get; set; }
public Opportunity TheOpp{
get {
TheOpp = [select Id FROM Opportunity];
return TheOpp;
}
set;
}
public Case TheCase{
get {
TheCase = [select Id FROM Case];
return TheCase;
}
set;
}
}
How do I fill the ContactsAndCases List?
A VisualForce component iterates over a single list. The type of the List, e.g. List<Lead>, dictates what data can be rendered by a VF component.
In order to render Lists of different types, you can either have a separate VF component for each type, e.g.:
<apex:pageBlockTable value="{!contacts}" var="contact">
...
</apex:pageBlockTable>
<apex:pageBlockTable value="{!cases}" var="case">
...
</apex:pageBlockTable>
Or, you can create a new type which holds instances of the different types. Here's a controller example:
public class MyController {
public List<ContactAndCase> ContactsAndCases { get; set; }
public MyController() {
ContactsAndCases = new List<ContactAndCase>();
// populate ContactsAndCases list
}
public class ContactAndCase {
public Contact TheContact { get; set; }
public Case TheCase { get; set; }
}
}
Then, you can iterate over a List<ContactAndCase>:
<apex:pageBlockTable value="{!ContactsAndCases}" var="item">
<apex:column value="{!item.TheContact.LastName}" />
<apex:column value="{!item.TheCase.CaseNumber}" />
</apex:pageBlockTable>
Jeremy's wrapper class is what you want. In addition, you'll generate SOQL-based lists of the objects you want first and then loop through to create new wrapper instances for your wrapper list (ContactsAndCases in this case) containing values from both contacts and cases.
Maybe I am missing something but couldn't you simply collect them in:
List<SObject> objects = new List<SObject>();
You can then add any generic SObject into that list.

How to get in a Visualforce page controller a value from a custom component controller?

I'm trying do develop a visualforce custom component which is an entity chooser. This custom component displays a UI which helps browsing some records. It's possible to select one record, and I'd like to get it from outside the component or its controller.
I've looked at the standard salesforce binding with assignTo bug it's not bidirectional...
Hope someone can help me..
Thanks
Are you passing an object into the component? Objects are passed by reference, so if your component has an attribute that takes an object and does something to it, your outer page controller will be able to access the changed values.
If you were to pass in a shell object, ie. if your UI is allowing a user to select an Account.
Class SelectedAccount
{
public Account theAccount {get;set;}
}
Component:
<apex:component controller="ComponentController">
<apex:attribute type="SelectedAccount" name="userSelectedAccount" description="Selected Account" assignTo="{!selectedAccount}"
</apex:component>
Component Controller:
public class ComponentController
{
public selectedAccount;
public void ComponentController(){}
public PageReference selectAccountFromUI(Account selected)
{
selectedAccount.theAccount = selected;
return null;
}
}
Page Using the Component:
<c:MyAccountComponent userSelectedAccount="{!instanceOfSelectedAccount}"/>
This would allow you to assign the user selected account into the instance of wrapper object which is owned by the outer controller. You can then reference:
instanceOfSelectedAccount.theAccount
from your main Visualforce Pages controller.
1 - Declare a static variable in the outside class (can be the VF page controller)
Something like :
public static apexType myRecordOutside;
2 -When you Make your choice from records in the method within the custom component controller
Do something like this :
OutsideClass.myRecordOutside = chosenRecord; //notice that when its static you can access it without instantiating the outside class.
3- then, declare in your Visual force
<c:myCustomComponent userSelectedAccount = {!myRecordOutside}></c:myCustomComponent>
this will get myRecordOutside not from the component's controller class, but from the outside class
If you have any question about a part of my answer let me know :)
/* This is an example of getting non static variable value
from visualforce component controller variable to visualforce page controller variable */
VF page: DisplayCountryPage
<apex:page>
<apex:commandButton value="display country list" action="{!displaycountryname}" />
<apex:repeat value="{!displaycountrylistvalue}" var="item">
<div>
{!item}
</div>
</apex:repeat>
<c:testvfcmp vfpageclasscontroller="{!thisPageInstance}"/>
</apex:page>
=====================
DisplayCountryPage VF Page controller: vfpageclass
public class vfpageclass{
public List<String> displaycountrylistvalue{get;set;}
public vfcomponentclass vfcmpobj{get;set;}
public void methodtosetvfcomponentclass(vfcomponentclass vfcmpobj2){
vfcmpobj = vfcmpobj2;
}
public void displaycountryname(){
displaycountrylistvalue = new List<String>();
displaycountrylistvalue = vfcmpobj.listOfCountry;
}
public vfpageclass thisPageInstance{
get{
return this;
}
set;
}
}
======================
vf component: testvfcmp
create an attribute like below:
<apex:component controller="CSTSearchPanelController">
<apex:attribute name="vfpageclasscontroller"
type="vfpageclass"
assignTo="{!vfpageobj}"
description="The controller for the page." />
<apex:commandButton value="set country list" action="{!setCountrylist}" />
</apex:component>
=====================
<testvfcmp> vf component controller: vfcomponentclass
public class vfcomponentclass{
public List<String> listOfCountry = new List<String>();
public vfpageclass vfpageobj{
get;
set{
vfpageobj = value;
vfpageobj.methodtosetvfcomponentclass(this);
}
}
public void setCountrylist(){
listOfCountry.add('India');
listOfCountry.add('USA');
}
}
/* This is an example of getting static variable value
from visualforce component controller variable to visualforce page controller variable */
VF page: DisplayCountryPage
<apex:page>
<apex:commandButton value="display country list" action="{!displaycountryname}" />
<apex:repeat value="{!displaycountrylistvalue}" var="item">
<div>
{!item}
</div>
</apex:repeat>
<c:testvfcmp vfpageclasscontroller="{!thisPageInstance}"/>
</apex:page>
=====================
DisplayCountryPage VF Page controller: vfpageclass
public class vfpageclass{
public List<String> displaycountrylistvalue{get;set;}
public void methodtosetvfcomponentclass(vfcomponentclass vfcmpobj2){
if(vfcmpobj2.getStaticCountryList() !=null){
displaycountrylistvalue = new List<String>();
displaycountrylistvalue = vfcmpobj2.getStaticCountryList();
}
/* USE THIS displaycountrylistvalue VARIABLE THROUGHOUT THE CLASS ONCE YOU SET BY HITTING BUTTON <set country list>.
DO NOT USE vfcmpobj2.getStaticCountryList() IN OTHER METHODS TO GET THE VALUE, IF DO, IT WILL RETURN NULL*/
}
public void displaycountryname(){
}
public vfpageclass thisPageInstance{
get{
return this;
}
set;
}
}
======================
vf component: testvfcmp
create an attribute like below:
<apex:component controller="CSTSearchPanelController">
<apex:attribute name="vfpageclasscontroller"
type="vfpageclass"
assignTo="{!vfpageobj}"
description="The controller for the page." />
<apex:commandButton value="set country list" action="{!setCountrylist}" />
</apex:component>
=====================
<testvfcmp> vf component controller: vfcomponentclass
public class vfcomponentclass{
public static List<String> listOfCountry = new List<String>();
public vfpageclass vfpageobj{
get;
set{
vfpageobj = value;
vfpageobj.methodtosetvfcomponentclass(this);
}
}
public static void setCountrylist(){
listOfCountry.add('India');
listOfCountry.add('USA');
}
public List<String> getStaticCountryList(){
return listOfCountry;
}
}

Resources