Visualforce can solve lots of different kinds of problems – including ones that we might stumble upon simply as developers and not necessarily as users.  If you are familiar with the Streaming API, you know that the API uses objects called PushTopics to define the topics that clients can get a subscription.  There currently isn’t any browser based interface for managing these topics – which isn’t that much of a hassle since you can control everything via Apex.  It is pretty easy to create, query, delete, and manipulate the topics using the Developer Console – but if you happen to be managing multiple topics, it can become something of a burden.

So I created a quick Visualforce page to help manage the ones I had been testing out in my dev org.  Having spent a lot of time lately tinkering with jQuery and JavaScript Remoting based interfaces, I also wanted to use this as an excuse to go back to developing with a normal viewstate-based form, especially since I knew it would probably easier to bind the PushTopic object against the apex form tags than to roll my own solution for it.

Designing the Controller

I know I would need a couple of things:

  1. A list of the topics currently in the system
  2. A blank, or placeholder, PushTopic to either create or update the topic with the bound fields
  3. A placeholder ID so that I can track which PushTopic I’m currently editing
With that, and a constructor which queries for the existing topics and sets the placeholders to null or blank – I have some code which looks like this:
public with sharing class PushTopicController {

	public List<PushTopic> pushtopics {get; set;}
	public PushTopic currentPushTopic {get; set;}

	public String editTopicId {get; set;}

	public PushTopicController() {
		pushtopics = new List<PushTopic>();
		pushtopics = [SELECT Id, Name, Query, Description, ApiVersion, NotifyForOperations, NotifyForFields from PushTopic];
		currentPushTopic = null;
		editTopicId = '';
	}

}

Designing the Visualforce

While my controller doesn’t actually do anything yet, I can at least set the page up to test out my list and setup a form that I can wire things against.  I’m going to block out a few OutputPanels to break up the page, and in one drop a repeat component against my current list of topics and in other setup the empty form.

	<apex:form>	

	<apex:outputPanel id="list" >
	<div style="float: left; width: 250px;">
		<UL id="topicsList">
		<apex:repeat value="{!pushtopics}" var="topic">
		<LI><apex:outputText value="{!topic.Name}" /></LI>
		</apex:repeat>
		</UL>

	</div>
	</apex:outputPanel>

	<apex:outputPanel id="object" >
	<div style="float: right; width: 350px;">
	<apex:outputPanel id="form"  >
		<apex:pageBlock>

		<apex:pageBlockSection>
		<apex:inputField value="{!currentPushTopic.Name}" /><BR />
		<apex:inputField value="{!currentPushTopic.Description}" /><BR />
		<apex:inputField value="{!currentPushTopic.Query}"  /><BR />
		<apex:inputField value="{!currentPushTopic.ApiVersion}" /><BR />
		<apex:inputField value="{!currentPushTopic.NotifyForOperations}" />
			<em>a = 'all', c = 'create', u = 'update'</em><BR />
		<apex:inputField value="{!currentPushTopic.NotifyForFields}" />
			<em>s = 'select', w = 'where', r = 'referenced', a = 'all'</em><BR />

		</apex:pageBlockSection>

		</apex:pageBlock>
	</apex:outputPanel>
	</div>
	</apex:outputPanel>

</apex:form>

Since my currentPushTopic is null, the fields are just going to be blank.  However, if you go to any of the fields and just hit enter – you’ll note that they are in fact tightly bound to the (empty) PushTopic record.  Even though we aren’t explicitly telling Visualforce to save this record, the rules engine checks the current validation when the viewstate is submitting the information back to the server.  Normally this is exactly the kind of behavior you want: a server-side validation system which is going to block any kind of bad data being inserted into the system.  When you are dealing with one record and one form at a time (usually the case) – this isn’t much of an issue.  However, if we add a new method on our controller to update the currentPushTopic via our ID field:

	public PageReference setCurrentTopic() {
		if(editTopicId == 'New') { currentPushTopic = new PushTopic(ApiVersion=26.0,NotifyForOperations='All',NotifyForFields='Referenced'); }
		else {
			for(PushTopic t : pushtopics) {
				if(t.Id == Id.valueOf(editTopicId)) { currentPushTopic = t; }
			}
		}
		return null;
	}

And then update the list in our component to interact with it:

	<apex:outputPanel id="list" >
	<div style="float: left; width: 250px;">
		<UL id="topicsList">
		<apex:repeat value="{!pushtopics}" var="topic">
		<LI><apex:commandButton action="{!setCurrentTopic}" rerender="object, list" value="{!topic.Name}" >
						<apex:param name="topicId{!topic.Id}" value="{!topic.Id}" assignTo="{!editTopicId}" />
			</apex:commandButton><BR />
			<apex:outputText value="{!topic.Description}" /><BR />
		</LI>
		</apex:repeat>
		<LI><apex:commandButton action="{!setCurrentTopic}" rerender="object, list" value="New Topic" >
							<apex:param value="New" assignTo="{!editTopicId}" />
			</apex:commandButton></LI>
		</UL>

	</div>
	</apex:outputPanel>

And then test it out – you’ll see that in this case the validation rules will stop even the basic updating of the record we are looking at … and in fact block our controller from doing what we want to perform.  There’s several ways around this problem – one very simple way is to use a wizard-based interface with Visualforce and Apex which goes from page to page while only displaying exactly what a user needs.  It is a very straightforward way of fixing the issue, but felt heavy handed here – after all, I just have two working aspects to the page.

Controlling Visualforce rendering with formulas

Another simple solution is to just not render any element on a Visualforce page until we are actually ready to use it.  Elements not currently rendered are not held within the current viewstate and hence aren’t going to be evaluated by validation rules.  We have effectively two modes for our users: 1) selecting a push topic (even a blank one) and 2) editing that push topic and saving it back to the system.  By default, our currentPushTopic is null and can’t be edited – in our first mode, the user will either choose one or create a new instance of one.  This means we can easily check which mode we are in by adding some equations on if the record is null or not:

<apex:form>

<apex:outputPanel id="list" >
<div style="float: left; width: 250px;">
	<UL id="topicsList">
	<apex:repeat value="{!pushtopics}" var="topic">
	<LI><apex:commandButton action="{!setCurrentTopic}" rerender="object, list" value="{!topic.Name}" disabled="{!currentPushTopic != null}">
					<apex:param name="topicId{!topic.Id}" value="{!topic.Id}" assignTo="{!editTopicId}" />
		</apex:commandButton><BR />
		<apex:outputText value="{!topic.Description}" /><BR />
	</LI>
	</apex:repeat>
	<LI><apex:commandButton action="{!setCurrentTopic}" rerender="object, list" value="New Topic" disabled="{!currentPushTopic != null}">
						<apex:param value="New" assignTo="{!editTopicId}" />
		</apex:commandButton></LI>
	</UL>

</div>
</apex:outputPanel>

<apex:outputPanel id="object" >
<div style="float: right; width: 350px;">
<apex:outputPanel id="form" rendered="{!currentPushTopic != null}" >
<apex:pageBlock>

<apex:pageBlockSection>
<apex:inputField value="{!currentPushTopic.Name}" /><BR />
<apex:inputField value="{!currentPushTopic.Description}" /><BR />
<apex:inputField value="{!currentPushTopic.Query}"  /><BR />
<apex:inputField value="{!currentPushTopic.ApiVersion}" /><BR />
<apex:inputField value="{!currentPushTopic.NotifyForOperations}" />
	<em>a = 'all', c = 'create', u = 'update'</em><BR />
<apex:inputField value="{!currentPushTopic.NotifyForFields}" />
	<em>s = 'select', w = 'where', r = 'referenced', a = 'all'</em><BR />

</apex:pageBlockSection>

</apex:pageBlock>

</apex:outputPanel>
</div>
</apex:outputPanel>
</apex:form>

Now we can select our push topic, which will give the currentPushTopic an actual instance – and in turn disable the commandButtons in our list while allowing the form to render.

Controlling Visualforce with actionRegion

We don’t really have the functionality we need at the moment, we’re missing some basic actions in our controller to actually insert, update and delete our topic.  We’ll also add a method which just does nothing but set the currentPushTopic back to null so that we can effectively cancel out editing without making any changes:

public PageReference saveCurrentTopic() {
	try {
		upsert currentPushTopic;
		pushtopics = [SELECT Id, Name, Query, Description, ApiVersion, NotifyForOperations, NotifyForFields from PushTopic];
		currentPushTopic = null;
		return null;
	} catch (Exception e) {
		return null;
	}

}

public PageReference deleteCurrentTopic() {
	delete currentPushTopic;
	pushtopics = [SELECT Id, Name, Query, Description, ApiVersion, NotifyForOperations, NotifyForFields from PushTopic];
	currentPushTopic = null;
	return null;
}

public PageReference cancelCurrentTopic() {
	currentPushTopic = null;
	return null;
}

Now if we just add buttons to our form for each of our actions – we run into a similar problem that we had before.  Canceling the action, for instance, could happen before any form fields have been filled out … but since we are rendering our form, the validation rules are going to kick in once again.  To solve this problem, we use a component designed specifically for this problem: actionRegion.  The actionRegion component defines a section of the page that is the only portion to be submitted back with the viewstate, and hence elements outside that focus won’t be effected.  So we’ll add our buttons in with actionRegion in mind:

<apex:outputPanel id="form" rendered="{!currentPushTopic != null}" >
	<apex:pageBlock>

	<apex:PageBlockButtons>
		<apex:commandButton action="{!saveCurrentTopic}" rerender="object, list" value="Upsert" />
		<apex:actionRegion>
			<apex:commandButton onmousedown="if(confirm('Really?')){deleteCurrentTopic();}" value="Delete" disabled="{!currentPushTopic.Id == null}" />
			<apex:actionFunction action="{!deleteCurrentTopic}" name="deleteCurrentTopic" rerender="object, list" />
		</apex:actionRegion>
		<apex:actionRegion>
			<apex:commandButton action="{!cancelCurrentTopic}" rerender="object, list" value="Cancel" />
		</apex:actionRegion>
	</apex:PageBlockButtons>

	<apex:pageBlockSection>
	<apex:inputField value="{!currentPushTopic.Name}" /><BR />
	<apex:inputField value="{!currentPushTopic.Description}" /><BR />
	<apex:inputField value="{!currentPushTopic.Query}"  /><BR />
	<apex:inputField value="{!currentPushTopic.ApiVersion}" /><BR />
	<apex:inputField value="{!currentPushTopic.NotifyForOperations}" />
		<em>a = 'all', c = 'create', u = 'update'</em><BR />
	<apex:inputField value="{!currentPushTopic.NotifyForFields}" />
		<em>s = 'select', w = 'where', r = 'referenced', a = 'all'</em><BR />

	</apex:pageBlockSection>

	</apex:pageBlock>

	</apex:outputPanel>

See the full example

Now we have a pretty simple Visualforce page and Apex controller which makes managing our PushTopics more visual.  The entire Visualforce page, including additional styling, and Controller with unit tests can be found at this gist.  Shoot me questions or comments in either the boxes below, or catch me on twitter @joshbirk.  Here’s how the end result looks:

tagged , , , Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • http://twitter.com/shobyabdi shoby abdi

    This is excellent! I’m using this today!

  • Nayana Sebastian

    Simple and brilliant…and very useful.!