6

I have a Visualforce page using a custom controller that is used to edit multiple records under an opportunity.

I'd like to create a custom button or link from Opportunities to this Visualforce page.

Currently the link looks like:

/apex/ExamplePage?oppId={!Opportunity.Id}

This works fine in the development sandbox, but when it is deployed as part of a managed package the link breaks as the page reference doesn't have the namespace prefix.

I found the post Managed Package Redirecting Problem on the Force.com Discussion Boards which implied it should be possible to use $Page to reference the Visualforce page in the URL. E.g.

{!URLFOR($Page.MyExamplePage,'',[objectId = campaign.id])}

But doing so only gives me the syntax error:

Error: Field $Page.MyExamplePage does not exist. Check spelling.

There is another part to the post that suggests using an Apex class and Execute Javascript to work around it. But it appears to me that this has just moved the namespace issue into the Javascript.

How can I safely reference the Visualforce page to work both inside and outside a managed package?

Daniel Ballinger
  • 11,767
  • 10
  • 66
  • 93
  • 1
    I found [Custom Button: Expose $Page in the URL Content Type](https://sites.secure.force.com/success/ideaView?id=08730000000aVIfAAM) on the IdeaExchange which would be good to promote. – Daniel Ballinger Dec 15 '11 at 00:56

4 Answers4

7

Best to do this from an Apex PageReference return value. Something like this will work:

public PageReference returnPage()
{
   return Page.MyExamplePage;
}

Then call this from Visualforce:

<apex:commandButton value="Go To New Page" action="{!returnPage}"/>

The Apex Page call will handle the translation for you.

[EDIT]

Create a bare bones Visualforce page like this:

<apex:page standardController="Opportunity" extensions="TheController" action="{!returnPage}"/>

Add the above returnPage() method to a new TheController (or whatever) class. It doesn't even need a constructor. The class can look like this:

public TheController
{
   public PageReference returnPage()
   {
      return Page.MyExamplePage;
   }
}

Then from the Opportunity settings page go to Buttons and Links and create a new custom Visualforce button selecting the new page you just created.

That should do it.

Adam
  • 2,595
  • 1
  • 14
  • 20
  • I need to display the link to the Visualforce page from the standard Opportunity Detail page. With a custom Detail Page Button the user is free to position this button using the Page Layout Editor. Will your suggestion work similar to this? If my understanding is correct I'd need a custom controller for the returnPage() method and some way to put the commandButton on the Opportunities detail page. – Daniel Ballinger Dec 14 '11 at 01:41
  • A custom Apex controller will be required. As long as it references standardController="Opportunity" and extensions="NewCustomController" in the apex:page definition you will be fine. I'll modify my answer to fit your situation. It involves a little trick when launching directly from an Opportunity detail page. – Adam Dec 14 '11 at 02:34
  • Thanks, that works. It does create a slight bounce as the user passes through the intermediate page but it addresses the PageReference issue. I was able to add additional parameters to the PageReference by using a constructor that took the ApexPages.StandardController as a argument. My only concern now is I've added an additional page and apex class for every custom button. – Daniel Ballinger Dec 15 '11 at 01:39
  • http://salesforce.stackexchange.com/questions/28974/determining-namespace-prefix-in-javascript-and-apex – Gaurav Saini Jul 29 '16 at 10:28
0

It seems the answer is simply /apex/package__Page as provided here by @zachelrath. I can confirm this works in managed packages in production orgs as well as in development.

Community
  • 1
  • 1
Marc
  • 9,693
  • 10
  • 50
  • 66
0

The post on the developer boards that you've linked to shows the following javascript being used for the button:

{!REQUIRESCRIPT("/soap/ajax/15.0/connection.js")}
{!REQUIRESCRIPT("/soap/ajax/15.0/apex.js")}
var pageUrl = sforce.apex.execute("mynamespace.PageUrl", "getPageUrl", {objectId:"{!Campaign.Id}"});
window.location.href =  pageUrl;

i.e. they're using javascript to call a webservice method in the class they've defined in order to get the page reference. Doing this would allow you to get the URL of the page in apex, where the managed package won't play an impacting part.

That said, the first parameter is the fully-qualified class name, so you could probably check the return value for an error (I don't know the error return value, so I'm assuming it's null here):

// try the namespace first
var pageUrl = sforce.apex.execute("mynamespace.myClass", "getPageUrl", {objectId:"{!Campaign.Id}"});
if (pageUrl == null)
{
    pageUrl = sforce.apex.execute("myClass", "getPageUrl", {objectId:"{!Campaign.Id}"});
}

window.location.href = pageUrl;

Obviously you need to check what happens when sforce.apex.execute() fails, and you'll likely want some more error handling.

Daniel Ballinger
  • 11,767
  • 10
  • 66
  • 93
Matt Lacey
  • 8,077
  • 30
  • 57
  • Thanks, this is what I was thinking. The issue with the managed package namespace has been moved into the Javascript and needs to be handled there instead. I guess I'll give this a try and see how it goes. – Daniel Ballinger Dec 14 '11 at 01:35
0

It occurred to me that one less than ideal option would be to create two custom buttons in each case. One with the managed package namespace and one without.

When building the package the correct custom button could be selected.

One issue with this approach is the need to maintain two custom buttons.

Daniel Ballinger
  • 11,767
  • 10
  • 66
  • 93
  • How would the managed button URL formula be coded?. We are seeing that the entire host for our packaged pages is different once installed. We see something like `ourpackage.cs8.visual.force.com`! – Marc Mar 19 '13 at 13:50
  • @Marc, I went with the apex based solution so I didn't need to think about the domain or the managed package namespace. – Daniel Ballinger Mar 19 '13 at 23:48