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>
<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>:
<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}')" />
<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>
Here is one possible solution that can be filed under - It's Ugly, But It Works.
- On the Visualforce page, add an actionFunction that
is connected to a no op method and rerenders the pageBlock that contains the pageBlockButtons. This gives us a javascript method we can call to rerender the buttons.
actionFunction for the Page:
<apex:actionFunction name="checkAddFilterButton" rerender="selectPositionsPageBlock"/>
Call the resulting javascript function from the onComplete attribute of the actionSupport in the Visualforce component.
Now when the actionSupport call finishes in the compoent it will call the javascript method to update the buttons. It's ugly on a number of levels, such as multiple calls to the server, the slight delay before the button gets enabled and having to refresh the entire page block.
- Set the apex:pageBlockButtons location to be either "top" or "bottom", but not "both". This will result in just a single set of controls to be turned on/off.
There is an undesirable side effect of this approach. Between the actionSupport completing and the actionFunction completing there is a period of time where the user can make changes to the controls within the pageBlock. When the actionFunction completes those changes will be reversed.
From the users perspective they start changing something, and then it reverts back to the value it had when they changed the select.
Update: I fixed this by hacking an apex:outputPanel
into the apex:pageBlockButtons
and then having the actionFunction rerender that instead of the entire pageBlock. ... Hacks all the way down...
I'd be happy to accept a more refined answer than this solution.
Best Answer
I'm surprised this doesn't work but then I can't remember if I've ever tried it.
Given the global nature of javascript variables and elements used in components you could put an
<apex:outputPanel>
inside your component wrapping everything else, and give it a particular ID.Then specify that ID in the rerender attribute of the
<apex:actionFunction>
as well.