Skip to content

Instantly share code, notes, and snippets.

@greenstork
Last active August 29, 2015 14:10
Show Gist options
  • Save greenstork/4fd5420dad2df374ff24 to your computer and use it in GitHub Desktop.
Save greenstork/4fd5420dad2df374ff24 to your computer and use it in GitHub Desktop.
Visualforce/JS Feedback widget
<div id="feedback">
<div id="feedback-widget" style="z-index:300;display:none;">
<a class="handle" style="border-left:1px solid #ddd;border-top:1px solid #ddd;border-bottom:1px solid #ddd;" href="#">Feedback</a>
<div id="feedback-frame" style="display:none;">
</div>
</div>
</div>
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<actionOverrides>
<actionName>Accept</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Clone</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Delete</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>List</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>New</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Tab</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<type>Default</type>
</actionOverrides>
<deploymentStatus>Deployed</deploymentStatus>
<enableActivities>false</enableActivities>
<enableEnhancedLookup>false</enableEnhancedLookup>
<enableFeeds>true</enableFeeds>
<enableHistory>false</enableHistory>
<enableReports>true</enableReports>
<fields>
<fullName>Message__c</fullName>
<externalId>false</externalId>
<label>Message</label>
<length>32768</length>
<trackFeedHistory>false</trackFeedHistory>
<type>LongTextArea</type>
<visibleLines>5</visibleLines>
</fields>
<fields>
<fullName>Page_Name__c</fullName>
<externalId>false</externalId>
<formula>LOWER(SUBSTITUTE(IF(CONTAINS(URL__c, &quot;?&quot;), LEFT((RIGHT(URL__c, LEN(URL__c) - FIND(&quot;.com/&quot;, URL__c) - 4)), FIND(&quot;?&quot;, RIGHT(URL__c, LEN(URL__c) - FIND(&quot;.com/&quot;, URL__c) - 4)) - 1), RIGHT(URL__c, LEN(URL__c) - FIND(&quot;.com/&quot;, URL__c) - 4)), &quot;apex/&quot;, &quot;&quot;))</formula>
<label>Page Name</label>
<required>false</required>
<type>Text</type>
<unique>false</unique>
</fields>
<fields>
<fullName>Rating__c</fullName>
<externalId>false</externalId>
<label>Rating</label>
<precision>1</precision>
<required>false</required>
<scale>0</scale>
<trackFeedHistory>false</trackFeedHistory>
<type>Number</type>
<unique>false</unique>
</fields>
<fields>
<fullName>Status__c</fullName>
<externalId>false</externalId>
<inlineHelpText>Status is this feedback. Has it been processed and has the person submitting received a response?</inlineHelpText>
<label>Status</label>
<picklist>
<picklistValues>
<fullName>New</fullName>
<default>true</default>
</picklistValues>
<picklistValues>
<fullName>In Progress</fullName>
<default>false</default>
</picklistValues>
<picklistValues>
<fullName>Completed</fullName>
<default>false</default>
</picklistValues>
<sorted>false</sorted>
</picklist>
<trackFeedHistory>false</trackFeedHistory>
<type>Picklist</type>
</fields>
<fields>
<fullName>URL__c</fullName>
<externalId>false</externalId>
<label>URL</label>
<required>false</required>
<trackFeedHistory>false</trackFeedHistory>
<type>Url</type>
</fields>
<label>Feedback</label>
<listViews>
<fullName>All</fullName>
<columns>NAME</columns>
<columns>URL__c</columns>
<columns>Page_Name__c</columns>
<columns>Rating__c</columns>
<columns>Message__c</columns>
<columns>CREATED_DATE</columns>
<columns>OWNER.FIRST_NAME</columns>
<columns>OWNER.LAST_NAME</columns>
<filterScope>Everything</filterScope>
<label>All</label>
<language>en_US</language>
</listViews>
<listViews>
<fullName>Unresolved_Feedback</fullName>
<columns>CREATEDBY_USER</columns>
<columns>NAME</columns>
<columns>Message__c</columns>
<columns>Status__c</columns>
<filterScope>Everything</filterScope>
<filters>
<field>Status__c</field>
<operation>equals</operation>
<value>New,In Progress</value>
</filters>
<label>Unresolved Feedback</label>
<language>en_US</language>
</listViews>
<nameField>
<displayFormat>F-{0000000}</displayFormat>
<label>Feedback Name</label>
<trackFeedHistory>false</trackFeedHistory>
<type>AutoNumber</type>
</nameField>
<pluralLabel>Feedback</pluralLabel>
<searchLayouts>
<customTabListAdditionalFields>URL__c</customTabListAdditionalFields>
<customTabListAdditionalFields>Rating__c</customTabListAdditionalFields>
<customTabListAdditionalFields>Message__c</customTabListAdditionalFields>
<customTabListAdditionalFields>OWNER.FIRST_NAME</customTabListAdditionalFields>
<customTabListAdditionalFields>OWNER.LAST_NAME</customTabListAdditionalFields>
<searchResultsAdditionalFields>URL__c</searchResultsAdditionalFields>
<searchResultsAdditionalFields>Rating__c</searchResultsAdditionalFields>
<searchResultsAdditionalFields>Message__c</searchResultsAdditionalFields>
</searchLayouts>
<sharingModel>Private</sharingModel>
</CustomObject>
<script src="/resource/Bootstrap3/js/jquery-1.10.2.min.js" ></script>
<script src="/resource/pubstyles/js/jquery.tabSlideOut.v1.3.js" ></script>
<script type="text/javascript">
var j$ = jQuery.noConflict();
j$(document).ready(function(){
j$('#feedback-frame').html('<iframe width="300" height="260" src="/apex/PUBfeedback?url='+ window.location.href + '" frameBorder="0"></iframe>');
j$('.handle').click( function() {
j$('#feedback-frame').toggle('fast');
});
j$(function(){
j$('#feedback-widget').tabSlideOut({
tabHandle: '.handle', //class of the element that will be your tab
pathToTabImage: '/resource/pubstyles/img/feedback.gif', //path to the image for the tab (optionaly can be set using css)
imageHeight: '90px', //height of tab image
imageWidth: '30px', //width of tab image
tabLocation: 'right', //side of screen where tab lives, top, right, bottom, or left
speed: 300, //speed of animation
action: 'click', //options: 'click' or 'hover', action to trigger animation
topPos: '200px', //position from the top
fixedPosition: false //options: true makes it stick(fixed position) on scroll
});
j$('#feedback-widget').show();
});
</script>
<apex:page controller="PUBfeedbackController" showHeader="false" sidebar="false" standardstylesheets="false">
<apex:includeScript value="{!URLFOR($Resource.jQuery, 'jQuery/jquery-1.8.3.min.js')}" />
<apex:styleSheet value="{!URLFOR($Resource.bootstrap, 'css/bootstrap.min.css')}" />
<apex:includeScript value="{!URLFOR($Resource.JQuery_Raty, 'js/jquery.raty.js')}" />
<apex:styleSheet value="{!URLFOR($Resource.JQuery_Raty, 'css/application.css')}" />
<script>
var j$ = jQuery.noConflict();
j$(document).ready(function(){
j$('#star').raty({
path: "{!URLFOR($Resource.JQuery_Raty, 'img/')}",
click: function(score, evt) {
j$('[id$="feedbackRating"]').val(score);
}
});
});
</script>
<body style="background-color:rgb(247, 247, 249);margin:0px;padding:0px;">
<apex:form style="margin:0px;">
<fieldset style="height:247px;margin:0;">
<apex:outputPanel rendered="{!NOT(submitted)}">
<label>Rate this page</label>
<div style="margin-bottom:10px;" id="star"></div>
<label>Message</label>
<apex:inputTextArea value="{!feedback.Message__c}" rows="5" style="width:95%"></apex:inputTextArea>
<div style="margin-top:10px;">
<apex:commandLink styleClass="btn" action="{!Save}">Submit</apex:commandLink>
</div>
</apex:outputPanel>
<apex:outputPanel rendered="{!submitted}">
<div class="alert alert-success">
<span style="font-size:14px;">Thank you for your feedback!</span>
</div>
</apex:outputPanel>
</fieldset>
<apex:inputHidden id="feedbackRating" value="{!feedback.Rating__c}" />
</apex:form>
</body>
</apex:page>
public with sharing class PUBfeedbackController {
public Feedback__c feedback { get; set; }
public boolean submitted { get; private set; }
public PUBfeedbackController() {
feedback = new Feedback__c();
map<string,string> params = apexPages.currentPage().getParameters();
feedback.URL__c = params.get('url');
}
public pageReference Save() {
//only try to save a record if some feedback was given, otherwise just do nothing
//we are not opting to incorporate validation since this is such a simple form
if (feedback.Rating__c != null || feedback.Message__c != null) {
try {
insert feedback;
submitted = true;
} catch (Exception e) {
WLMA_Error_Logger.log(e, 'PUBfeedbackController', 'Save', null);
}
}
return null;
}
/****************************************
* TESTS *
****************************************/
static testMethod void submitFeedback() {
Test.setCurrentPageReference(new PageReference('PUBfeedback.myPage'));
System.currentPageReference().getParameters().put('url', 'http://www.google.com/');
PUBfeedbackController controller = new PUBfeedbackController();
controller.feedback.Message__c = 'The PUB rocks, man, far out';
controller.feedback.Rating__c = 5;
controller.Save();
Feedback__c[] feedback = [SELECT Message__c, Rating__c, URL__c FROM Feedback__c LIMIT 1];
system.assertEquals(feedback[0].Message__c, 'The PUB rocks, man, far out');
system.assertEquals(feedback[0].Rating__c, 5);
system.assertEquals(feedback[0].URL__c, 'http://www.google.com/');
}
static testMethod void throwFeedbackInsertError() {
Test.setCurrentPageReference(new PageReference('PUBfeedback.myPage'));
System.currentPageReference().getParameters().put('url', 'http://www.google.com/');
PUBfeedbackController controller = new PUBfeedbackController();
controller.feedback.Message__c = 'The PUB rocks, man, far out';
//this should throw an error, since this is a one digit number field
controller.feedback.Rating__c = 56;
try {
controller.Save();
} catch (Exception e) {
system.assert(e != null);
}
}
static testMethod void noInsert() {
Test.setCurrentPageReference(new PageReference('PUBfeedback.myPage'));
System.currentPageReference().getParameters().put('url', 'http://www.google.com/');
PUBfeedbackController controller = new PUBfeedbackController();
controller.Save();
//there was no Rating or Message, so no record should have been inserted
Feedback__c[] feedback = [SELECT Message__c, Rating__c, URL__c FROM Feedback__c LIMIT 1];
system.assert(feedback.isEmpty());
}
}
/*
tabSlideOUt v1.3 (altered by katowulf)
Originally by William Paoli: http://wpaoli.building58.com
To use you must have an image ready to go as your tab
Make sure to pass in at minimum the path to the image and its dimensions:
example:
$('.slide-out-div').tabSlideOut({
tabHandle: '.handle', //class of the element that will be your tab -doesnt have to be an anchor
pathToTabImage: 'images/contact_tab.gif', //relative path to the image for the tab
imageHeight: '133px', //height of tab image
imageWidth: '44px', //width of tab image
});
or you can leave out these options
and set the image properties using css
*/
(function($){
$.fn.tabSlideOut = function(callerSettings) {
var settings = $.extend({
tabHandle: '.handle',
speed: 300,
action: 'click',
tabLocation: 'left',
topPos: '200px',
leftPos: '20px',
fixedPosition: false,
positioning: 'absolute',
pathToTabImage: null,
imageHeight: null,
imageWidth: null,
onLoadSlideOut: false,
tabHandleOffset: 0
}, callerSettings||{});
settings.tabHandle = $(settings.tabHandle);
var obj = this;
if (settings.fixedPosition === true) {
settings.positioning = 'fixed';
} else {
settings.positioning = 'absolute';
}
//ie6 doesn't do well with the fixed option
if (document.all && !window.opera && !window.XMLHttpRequest) {
settings.positioning = 'absolute';
}
//set initial tabHandle css
if (settings.pathToTabImage != null) {
settings.tabHandle.css({
'background' : 'url('+settings.pathToTabImage+') no-repeat',
'width' : settings.imageWidth,
'height': settings.imageHeight
});
}
settings.tabHandle.css({
'display': 'block',
'textIndent' : '-99999px',
'outline' : 'none',
'position' : 'absolute'
});
obj.css({
'line-height' : '1',
'position' : settings.positioning
});
var properties = {
containerWidth: parseInt(obj.outerWidth(), 10) + 'px',
containerHeight: parseInt(obj.outerHeight(), 10) + 'px',
tabWidth: parseInt(settings.tabHandle.outerWidth(), 10) + 'px',
tabHeight: parseInt(settings.tabHandle.outerHeight(), 10) + 'px'
};
//set calculated css
if(settings.tabLocation === 'top' || settings.tabLocation === 'bottom') {
obj.css({'left' : settings.leftPos});
var tabRightOffset = settings.tabHandleOffset==='center'? Math.floor(obj.outerWidth()/2)+'px' : settings.tabHandleOffset;
settings.tabHandle.css({'right' : tabRightOffset});
}
if(settings.tabLocation === 'top') {
obj.css({'top' : '-' + properties.containerHeight});
settings.tabHandle.css({'bottom' : '-' + properties.tabHeight});
}
if(settings.tabLocation === 'bottom') {
obj.css({'bottom' : '-' + properties.containerHeight, 'position' : 'fixed'});
settings.tabHandle.css({'top' : '-' + properties.tabHeight});
}
if(settings.tabLocation === 'left' || settings.tabLocation === 'right') {
obj.css({
'height' : properties.containerHeight,
'top' : settings.topPos
});
var tabTopOffset = settings.tabHandleOffset==='center'? Math.floor(obj.outerHeight()/2)+'px' : settings.tabHandleOffset;
settings.tabHandle.css({'top' : tabTopOffset});
}
if(settings.tabLocation === 'left') {
obj.css({ 'left': '-' + properties.containerWidth});
settings.tabHandle.css({'right' : '-' + properties.tabWidth});
}
if(settings.tabLocation === 'right') {
obj.css({ 'right': '-' + properties.containerWidth});
settings.tabHandle.css({'left' : '-' + properties.tabWidth});
$('html').css('overflow-x', 'hidden');
}
//functions for animation events
settings.tabHandle.click(function(event){
event.preventDefault();
});
var slideIn = function() {
if (settings.tabLocation === 'top') {
obj.animate({top:'-' + properties.containerHeight}, settings.speed).removeClass('open');
} else if (settings.tabLocation === 'left') {
obj.animate({left: '-' + properties.containerWidth}, settings.speed).removeClass('open');
} else if (settings.tabLocation === 'right') {
obj.animate({right: '-' + properties.containerWidth}, settings.speed).removeClass('open');
} else if (settings.tabLocation === 'bottom') {
obj.animate({bottom: '-' + properties.containerHeight}, settings.speed).removeClass('open');
}
};
var slideOut = function() {
if (settings.tabLocation == 'top') {
obj.animate({top:'-3px'}, settings.speed).addClass('open');
} else if (settings.tabLocation == 'left') {
obj.animate({left:'-3px'}, settings.speed).addClass('open');
} else if (settings.tabLocation == 'right') {
obj.animate({right:'-3px'}, settings.speed).addClass('open');
} else if (settings.tabLocation == 'bottom') {
obj.animate({bottom:'-3px'}, settings.speed).addClass('open');
}
};
var clickScreenToClose = function() {
obj.click(function(event){
event.stopPropagation();
});
$(document).click(function(){
slideIn();
});
};
var clickAction = function(){
settings.tabHandle.click(function(event){
if (obj.hasClass('open')) {
slideIn();
} else {
slideOut();
}
});
clickScreenToClose();
};
var hoverAction = function(){
obj.hover(
function(){
slideOut();
},
function(){
slideIn();
});
settings.tabHandle.click(function(event){
if (obj.hasClass('open')) {
slideIn();
}
});
clickScreenToClose();
};
var slideOutOnLoad = function(){
slideIn();
setTimeout(slideOut, 500);
};
//choose which type of action to bind
if (settings.action === 'click') {
clickAction();
}
if (settings.action === 'hover') {
hoverAction();
}
if (settings.onLoadSlideOut) {
slideOutOnLoad();
}
};
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment