Preventing a Visualforce ActionFunction from Refreshing the Page

I came across a restriction the other day regarding the apex:actionFunction Visualforce component when I was trying to implement a to-do list that allowed a user to complete a to-do with a click on a checkbox next to a list of to-dos. While using one of the many great two way binding Javascript frameworks like AngularJS with a @RemoteAction annotated Apex method would have worked great, it was a bit of overkill for this, as using a StandardSetController required minimal code.

Functionally, that all worked fine. The problem I ran into when I was setting up my code was that my entire Visualforce page was refreshing when I invoked the actionFunction. Even though I tried both setting the invoked Apex method to a return type of void (and later tried returning a null PageReference), the entire page refresh continued. Unfortunately this took away from the “behind the scenes save” behavior that I wanted the user to experience.

What I ended up doing to resolve this was setting a reRender attribute on the actionFunction that pointed to an ID that didn’t even exist on my page. While I didn’t have anything that I actually wanted to re-render, it appears that setting that attribute stopped the default action of a full page refresh and instead traded for the possibility of re-rendering an individual component instead.

The Visualforce page ended up looking like this very simple snippet of code:

<apex:page controller="TodosController">

  ...

  <apex:form>

    <apex:repeat value="{!todos}" var="todo">
      <apex:inputField value="{!todo.Completed__c}" onchange="saveUpdates()"/>
      <apex:outputField value="{!todo.Name}"/>
    </apex:repeat>

    <!-- need to point to nonexistent ID so page doesn't refresh -->
    <apex:actionFunction name="saveUpdates" action="{!saveUpdates}" rerender="fakeresults"/>

  </apex:form>

</apex:page>

Leveraging the StandardSetController makes implementing that custom saveUpdates method in the custom Apex controller quite easy:

public class TodosController {

  public ApexPages.StandardSetController stdSetController;

  ...

  public List<Todo__c> getTodos() {
    return this.stdSetController.getRecords();
  }

  public void saveUpdates() {
    this.stdSetController.save();
  }
}