[SalesForce] CommandLink calling JavaScript function does not ReRender

I am having issues getting a commandLink to call a javaScript function (javaScript remoting that calls an apex function) and then reRender an outputPanel in VisualForce. The JS function gets called, the apex function completes, but the outputPanel does not reRender.

The scenario is that I have created a VisualForce page to display a Chatter Group Feed. I have extended my controller to do the JS remoting. In the same page, I use a commandButton to post a new comment via JS remoting which works fine and reRenders the outputPanel. I have created the "Like" and "UnLike" links using commandLinks that call JS functions that perform the JS remoting and set the reRender attribute to the id of the outputPanel I would like to reRender after the JS remoting call has completed. The "Like" is added or removed, but the panel does not reRender to show the change.

Any help is appreciated. Here is the code for the VF page and the controller extension:

<apex:page controller="CanNewsChatterFeedController" extensions="CanNewsChatterFeedExtension" id="feedcontroller" sidebar="false" showHeader="false" standardStylesheets="false" >
<script type="text/javascript">
    var j$ = jQuery.noConflict();

    function likeFeedItem(itemId){
        CanNewsChatterFeedExtension.addLikeToFeedItem(itemId, handleAddLike);
    }

    function handleAddLike(result, event){
        return null;
    }

    function unLikeFeedItem(likeId){
        CanNewsChatterFeedExtension.removeLikeFromFeedItem(likeId, handleUnLike);
    }

    function handleUnLike(result, event){
        return null;
    }

    function showCommentBox(itemId){
        j$('#comBox_'+itemId).show();
    }

    function clearCommentToAdd(itemId){
        j$('#comIn_'+itemId).val('');
    }

    function addComment(itemId){
        var commentToAdd = j$('#comIn_'+itemId).val();
        CanNewsChatterFeedExtension.addComment(commentToAdd, itemId, handleAddComment);
    }

    function handleAddComment(result, event){
        return null;
    }
</script>
<style type="text/css">
    ....
</style>
<div id="feed-display-div">
    <apex:form >    
        <apex:actionPoller action="{!refreshFeed}" rerender="feedRows" interval="60" />
        <apex:outputPanel id="feedRows" >
            <apex:repeat value="{!newsFeedForDisplay}" var="feedItemInfo" rows="10" rendered="{!NOT(ISNULL(newsFeedForDisplay))}">
                <div class="row">
                    <div>     
                        <div style="display:inline-block;vertical-align:top;">
                            <a href="/{!feedItemInfo.userId}">
                                <apex:image style="margin:4px" width="40" url="{!feedItemInfo.feedItem.photoUrl}"/>
                            </a>
                        </div>

                        <div style="display:inline-block;vertical-align:top;width:220px; color:#087bf5;">
                            <a href="/{!feedItemInfo.userId}">
                                <b>{!feedItemInfo.userName}</b>
                            </a><br/>
                            <apex:outputText style="color:#000000;" value="{!feedItemInfo.formattedText}" />

                            <apex:outputPanel layout="block" rendered="{!IF(feedItemInfo.linkUrl == null, false, true)}" >              
                                <a href="{!feedItemInfo.linkUrl}">
                                    <b>{!feedItemInfo.linkTitle}</b>
                                </a>
                            </apex:outputPanel>

                            <apex:outputPanel >
                                <div class="comLikeDiv">
                                    <a href="#" onclick="showCommentBox('{!feedItemInfo.feedItemId}')">Comment</a><span class="separatorDot">.</span>
                                    <apex:outputPanel rendered="{!IF(feedItemInfo.isLikedByUser == true, false, true)}">
                                        <apex:commandLink onclick="likeFeedItem('{!feedItemInfo.feedItemId}')" value="Like" reRender="feedRows" /><span class="separatorDot">.</span>
                                    </apex:outputPanel>
                                    <apex:outputPanel rendered="{!IF(feedItemInfo.isLikedByUser == false, false, true)}">
                                        <apex:commandLink onclick="unLikeFeedItem('{!feedItemInfo.myLikeId}')" value="UnLike" reRender="feedRows" /><span class="separatorDot">.</span>
                                    </apex:outputPanel>
                                    &nbsp;<apex:outputText styleClass="createdDate" value="{!feedItemInfo.relativeCreatedDate}" />
                                </div>
                            </apex:outputPanel>
                        </div>

                        <apex:outputPanel layout="block" rendered="{!IF(feedItemInfo.contentDownloadUrl != null && feedItemInfo.imageUrl != null && feedItemInfo.hasImagePreview, true, false)}" >          
                            <div style="left: 3em; width: 85%; padding: 2px; position: relative;">    
                                <apex:image style="margin:4px" width="60" url="{!feedItemInfo.imageUrl}"/>
                                <div style="float:right;position:relative;padding:2px;width:65%;">
                                    <a href="{!feedItemInfo.contentDownLoadUrl}">Download:<br />{!feedItemInfo.contentTitle }</a>
                                </div> 
                            </div>               
                        </apex:outputPanel>
                    </div>

                    <apex:outputPanel rendered="{!IF(feedItemInfo.likesMessage != '', true, false)}">
                        <div class="likeBox">
                            <div style="margin-left:4px;width:95%;">
                                <div style="display:inline-block;vertical-align:top;">
                                    <apex:image value="{!$Resource.LikeThumb}" />
                                </div>
                                <div style="display:inline-block;width:80%;padding-top:2px;">
                                    <apex:outputText style="margin-left:4px;" value="{!feedItemInfo.likesMessage}" />
                                </div>
                            </div>
                        </div>
                    </apex:outputPanel>

                    <apex:outputPanel rendered="{!IF(feedItemInfo.commentCount > 0, true, false)}">
                        <div class="commentBox">
                            <apex:repeat value="{!feedItemInfo.comments}" var="commentInfo">
                                <div class="commentBox-inner">
                                    <div style="display:inline-block;vertical-align:top;">
                                        <apex:image style="margin:4px" width="25" url="{!commentInfo.comment.user.photo.smallPhotoUrl}"/>
                                    </div>

                                    <div style="display:inline-block;vertical-align:top;width:80%">
                                        <a href="/{!commentInfo.userId}">
                                            <b>{!commentInfo.userName}</b>
                                        </a>:&nbsp;
                                        <apex:outputText value="{!commentInfo.formattedText}" />
                                        <div class="createdDate">
                                            <apex:outputText value="{!commentInfo.relativeCreatedDate}" />
                                        </div>
                                    </div>

                                    <apex:outputPanel rendered="{!IF(commentInfo.imageUrl == null, false, true)}" >
                                        <div style="padding-left:4px;display:inline-block;vertical-align:top;float:left;position:relative;width:95%" >
                                            <apex:image style="padding:4px" width="60" url="{!commentInfo.imageUrl}"/>
                                            <div style="float:right;position:relative;padding:2px;width:65%;">
                                                <a href="{!commentInfo.contentDownLoadUrl}">Download:<br />{!commentInfo.contentTitle }</a> 
                                            </div>
                                        </div>
                                        <div style="clear: both;" />
                                    </apex:outputPanel>
                                </div>
                            </apex:repeat>
                        </div>
                    </apex:outputPanel>
                    <div class="addComment" id="comBox_{!feedItemInfo.feedItemId}">
                        <input type="text" id="comIn_{!feedItemInfo.feedItemId}" style="border:1px solid #AAAAAA;" value="Add a comment..." onfocus="clearCommentToAdd('{!feedItemInfo.feedItemId}')" />
                        &nbsp;<apex:commandButton onclick="addComment('{!feedItemInfo.feedItemId}')" value="Comment" reRender="feedRows" />
                    </div>
                </div>
            </apex:repeat>
        </apex:outputPanel>
    </apex:form>
</div>

public class CanNewsChatterFeedExtension {

// Constructor. Needed to use as an extension.
public CanNewsChatterFeedExtension(CanNewsChatterFeedController custController) {}

// Remote Action function lets JavaScript call Apex directly
// method to add the comment passed in from a Visualforce page JavaScript function to the feedItem that was passed in
@RemoteAction
public static Boolean addComment(String theComment, String theFeedItemId){
    try{
        FeedComment fcomment = new FeedComment();
        fcomment.FeedItemId = theFeedItemId; 
        fcomment.CommentBody = theComment; //String.escapeSingleQuotes(theComment); <--prevents SOQL injection. Necessary?
        insert fcomment;

        return true;

    }catch(Exception e){
        System.debug(e.getMessage());
        return false;
    }
}

@RemoteAction
public static Boolean addLikeToFeedItem(String theFeedItemId){
    try{
        ConnectApi.ChatterFeeds.likeFeedItem(null, theFeedItemId);

        return true;

    }catch(Exception e){
        System.debug(e.getMessage());
        return false;
    }
}

@RemoteAction
public static Boolean removeLikeFromFeedItem(String theLikeId){
    try{
        ConnectApi.ChatterFeeds.deleteLike(null, theLikeId);

        return true;

    }catch(Exception e){
        System.debug(e.getMessage());
        return false;
    }
   }
}

First Result: I got this to work with apex:commandLink by removing the rerender attribute. However, now, after returning from the remote action, the page refreshes and it's a little jumpy. Without an action attribute in the commandLink, I believe it's just refreshing the page not rerendering and that's why it's jumping.

Here's what I changed:

    <apex:outputPanel rendered="{!IF(feedItemInfo.isLikedByUser == true, false, true)}">
         <apex:commandLink onclick="likeFeedItem('{!feedItemInfo.feedItemId}')" value="Like" />
    </apex:outputPanel>
    <apex:outputPanel rendered="{!IF(feedItemInfo.isLikedByUser == false, false, true)}">
         <apex:commandLink onclick="unLikeFeedItem('{!feedItemInfo.myLikeId}')" value="Unlike" />
    </apex:outputPanel>

The last commandButton (used for adding comments and unchanged) works perfectly and reRenders the "feedRows" the way it should. I tried using re-styled commandButtons (the same way) to perform the "Like" actions, but they would not rerender the "feedRows". As I said, the changes I made to the commandLinks above work, but this is NOT the optimal solution. While the "Like" or "Unlike" is added or removed from the feedItem, the "feedRows" do not reRender. Due to the fact that there is no "action" attribute in the commandLinks, the page simply refreshes which is jumpy/choppy and not the optimal solution.

Any other ideas?

Best Answer

SOLUTION FOUND!!! The suggestion by Mohith Kumar to use actionFunction was the correct way to go. It just took a little research and better understanding of how actionFunction works to implement the solution. After some googling, I finally found a blog post (http://shivasoft.in/blog/salesforce/passing-parameter-in-actionfunction-in-visualforce/) that really helped me understand how to use actionFunction for my solution.

Here is the final code for the controller extension:

public with sharing class CanNewsChatterFeedExtension {
//declare class variables
public String feedItemId {get;set;}
public String likeId {get;set;}
public String commentToAdd {get;set;}

// Constructor. Needed to use as an extension.
public CanNewsChatterFeedExtension(CanNewsChatterFeedController custController) {}

//call via javascript and actionFunction. Class variables feedItemId and commentToAdd are set by passing params via actionFunction
public void addCommentToFeedItem(){
    try{
        FeedComment fcomment = new FeedComment();
        fcomment.FeedItemId = feedItemId; 
        fcomment.CommentBody = commentToAdd; //String.escapeSingleQuotes(commentToAdd); <--prevents SOQL injection. Necessary?
        insert fcomment;
    }catch(Exception e){
        System.debug(e.getMessage());
    }
}

//call via javascript and actionFunction. Class variable feedItemId is set by passing params via actionFunction
public void addLikeToFeedItem(){
    try{
        ConnectApi.ChatterFeeds.likeFeedItem(null, this.feedItemId);
    }catch(Exception e){
        System.debug(e.getMessage());
    }
}

//call via javascript and actionFunction. Class variable likeId is set by passing params via actionFunction
public void removeLikeFromFeedItem(){
    try{
        ConnectApi.ChatterFeeds.deleteLike(null, this.likeId);
    }catch(Exception e){
        System.debug(e.getMessage());
    }
}

}

And the VF page final code:

<apex:page controller="CanNewsChatterFeedController" extensions="CanNewsChatterFeedExtension" id="feedcontroller" sidebar="false" showHeader="false" standardStylesheets="false" >
<script type="text/javascript">
    var j$ = jQuery.noConflict();

    /*called via JS onclick to pass in the itemId. 
    When addLike() is called it fires the actionFunction of the same name defined below,
    which in turn passes the param to the apex class, sets the class variable and calls the defined apex method.
    */
    function likeFeedItem(itemId){
        addLike(itemId);
    }
    /*called via JS onclick to pass in the likeId. 
    When removeLike() is called it fires the actionFunction of the same name defined below,
    which in turn passes the param to the apex class, sets the class variable and calls the defined apex method.
    */
    function unLikeFeedItem(likeId){
        removeLike(likeId);
    }
    //displays the commentBox for adding new comments
    function showCommentBox(itemId){
        j$('#comBox_'+itemId).show();
    }
    //clears the text in the commentBox onFocus
    function clearCommentToAdd(itemId){
        j$('#comIn_'+itemId).val('');
    }
    /*called via JS onclick to pass in the itemId. 
    When addComment() is called it fires the actionFunction of the same name defined below,
    which in turn passes the params to the apex class, sets the class variables and calls the defined apex method.
    */
    function addCommentToFeedItem(itemId){
        var commentToAdd = j$('#comIn_'+itemId).val();
        addComment(itemId, commentToAdd);
    }

</script>
<style type="text/css">
    .....
</style>
<div id="feed-display-div">
    <apex:form >    
        <apex:pageMessages />
        <apex:actionPoller action="{!refreshFeed}" rerender="feedRows" interval="60" />
        <apex:outputPanel id="feedRows" >
            <apex:repeat value="{!newsFeedForDisplay}" var="feedItemInfo" rows="10" rendered="{!NOT(ISNULL(newsFeedForDisplay))}">
                <div class="row">
                    <div>     
                        <div style="display:inline-block;vertical-align:top;">
                            <a href="/{!feedItemInfo.userId}">
                                <apex:image style="margin:4px" width="40" url="{!feedItemInfo.feedItem.photoUrl}"/>
                            </a>
                        </div>

                        <div style="display:inline-block;vertical-align:top;width:220px; color:#087bf5;">
                            <a href="/{!feedItemInfo.userId}">
                                <b>{!feedItemInfo.userName}</b>
                            </a><br/>
                            <apex:outputText style="color:#000000;" value="{!feedItemInfo.formattedText}" />

                            <apex:outputPanel layout="block" rendered="{!IF(feedItemInfo.linkUrl == null, false, true)}" >              
                                <a href="{!feedItemInfo.linkUrl}">
                                    <b>{!feedItemInfo.linkTitle}</b>
                                </a>
                            </apex:outputPanel>

                            <div class="comLikeDiv">
                                <a href="#" onclick="showCommentBox('{!feedItemInfo.feedItemId}')">Comment</a><span class="separatorDot">.</span>
                                <apex:outputPanel rendered="{!IF(feedItemInfo.isLikedByUser == true, false, true)}">
                                    <span class="likeSpan" onclick="likeFeedItem('{!feedItemInfo.feedItemId}')">Like</span>
                                </apex:outputPanel>
                                <apex:outputPanel rendered="{!IF(feedItemInfo.isLikedByUser == false, false, true)}">
                                    <span class="likeSpan" onclick="unLikeFeedItem('{!feedItemInfo.myLikeId}')">Unlike</span>
                                </apex:outputPanel>
                                <span class="separatorDot">.</span>
                                &nbsp;<apex:outputText styleClass="createdDate" value="{!feedItemInfo.relativeCreatedDate}" />
                            </div>
                        </div>

                        <apex:outputPanel layout="block" rendered="{!IF(feedItemInfo.contentDownloadUrl != null && feedItemInfo.imageUrl != null && feedItemInfo.hasImagePreview, true, false)}" >          
                            <div style="left: 3em; width: 85%; padding: 2px; position: relative;">    
                                <apex:image style="margin:4px" width="60" url="{!feedItemInfo.imageUrl}"/>
                                <div style="float:right;position:relative;padding:2px;width:65%;">
                                    <a href="{!feedItemInfo.contentDownLoadUrl}">Download:<br />{!feedItemInfo.contentTitle }</a>
                                </div> 
                            </div>               
                        </apex:outputPanel>
                    </div>

                    <apex:outputPanel rendered="{!IF(feedItemInfo.likesMessage != '', true, false)}">
                        <div class="likeBox">
                            <div style="margin-left:4px;width:95%;">
                                <div style="display:inline-block;vertical-align:top;">
                                    <apex:image value="{!$Resource.LikeThumb}" />
                                </div>
                                <div style="display:inline-block;width:80%;padding-top:2px;">
                                    <apex:outputText style="margin-left:4px;" value="{!feedItemInfo.likesMessage}" />
                                </div>
                            </div>
                        </div>
                    </apex:outputPanel>

                    <apex:outputPanel rendered="{!IF(feedItemInfo.commentCount > 0, true, false)}">
                        <div class="commentBox">
                            <apex:repeat value="{!feedItemInfo.comments}" var="commentInfo">
                                <div class="commentBox-inner">
                                    <div style="display:inline-block;vertical-align:top;">
                                        <apex:image style="margin:4px" width="25" url="{!commentInfo.comment.user.photo.smallPhotoUrl}"/>
                                    </div>

                                    <div style="display:inline-block;vertical-align:top;width:80%">
                                        <a href="/{!commentInfo.userId}">
                                            <b>{!commentInfo.userName}</b>
                                        </a>:&nbsp;
                                        <apex:outputText value="{!commentInfo.formattedText}" />
                                        <div class="createdDate">
                                            <apex:outputText value="{!commentInfo.relativeCreatedDate}" />
                                        </div>
                                    </div>

                                    <apex:outputPanel rendered="{!IF(commentInfo.imageUrl == null, false, true)}" >
                                        <div style="padding-left:4px;display:inline-block;vertical-align:top;float:left;position:relative;width:95%" >
                                            <apex:image style="padding:4px" width="60" url="{!commentInfo.imageUrl}"/>
                                            <div style="float:right;position:relative;padding:2px;width:65%;">
                                                <a href="{!commentInfo.contentDownLoadUrl}">Download:<br />{!commentInfo.contentTitle }</a> 
                                            </div>
                                        </div>
                                        <div style="clear: both;" />
                                    </apex:outputPanel>
                                </div>
                            </apex:repeat>
                        </div>
                    </apex:outputPanel>
                    <div class="addComment" id="comBox_{!feedItemInfo.feedItemId}">
                        <input type="text" id="comIn_{!feedItemInfo.feedItemId}" style="border:1px solid #AAAAAA;" value="Add a comment..." onfocus="clearCommentToAdd('{!feedItemInfo.feedItemId}')" />
                        &nbsp;<apex:commandButton onclick="addCommentToFeedItem('{!feedItemInfo.feedItemId}')" value="Comment" rerender="feedRows" />
                    </div>
                </div>
            </apex:repeat>
            <apex:actionFunction name="addLike" action="{!addLikeToFeedItem}" rerender="feedRows">
                <apex:param name="theItemId" assignTo="{!feedItemId}" value="" />
            </apex:actionFunction>
            <apex:actionFunction name="removeLike" action="{!removeLikeFromFeedItem}" rerender="feedRows">
                <apex:param name="theLikeId" assignTo="{!likeId}" value="" />
            </apex:actionFunction>
            <apex:actionFunction name="addComment" action="{!addCommentToFeedItem}" rerender="feedRows">
                <apex:param name="theItemId" assignTo="{!feedItemId}" value="" />
                <apex:param name="theComment" assignTo="{!commentToAdd}" value="" />
            </apex:actionFunction>
        </apex:outputPanel>
    </apex:form>
</div>