Die Organisationsübergreifenden Sicherheitseinstellungen können den allgemeinen Zugriff auf den Datenbestand verhindern. Ist der Standardzugriff auf "Privat" gesetzt (s. Screenshot), hat nur der Eigentümer inklusive seines Vorgesetzten den Zugriff auf die Daten, wo er als Inhaber hinterlegt ist.
Insbesondere Vertriebsmitarbeiter arbeiten intensiv mit Account-Informationen. Hier werden unter anderem sensible Daten erfasst. In diesem Zusammenhang entstehen Fragen folgender Art:
Wie soll ich meine Salesforce-Organisation konfigurieren, wenn ich nur bestimmte Daten trotz "Privat" Einstellung allen Vertriebsmitarbeiter zur Verfügung stellen möchte?
Eine potenzielle Lösung ist die Entwicklung einer Visualforce-Seite, die relevante Daten darstellt. Basierend auf dem Beitrag von Jeff Douglas habe ich ein Suchformular gebaut. Damit lassen sich alle Accounts, unabhängig von den "Freigabeeinstellungen" durchsuchen.
Im folgenden Video ist die Funktionalität dargestellt.
Vorteile:
- der Zugriff auf die neue Seite und somit auf alle Daten ist konfigurierbar
- die Darstellung der Account-Felder lässt sich über ein Feldset steuern (keine Programmierung notwendig)
- schnelle Umsetzung
- flexibel für Anpassungen
Bausteine der Visualforce-Seite:
- CSS: was fürs Auge
- JavaScript:
* Übergabe der Suchparameter an den Controller
* Identifikation der Enter-Taste mit Ausführung der Suchfunktion
- ActionStatus: visuelle Darstellung für die Ausführung der Suchfunktion
Jetzt wird's technisch ;-)
------------------------------- VUSUALFORCE SEITE -------------------------------
------------------------------- CONTROLLER -------------------------------
Insbesondere Vertriebsmitarbeiter arbeiten intensiv mit Account-Informationen. Hier werden unter anderem sensible Daten erfasst. In diesem Zusammenhang entstehen Fragen folgender Art:
Wie soll ich meine Salesforce-Organisation konfigurieren, wenn ich nur bestimmte Daten trotz "Privat" Einstellung allen Vertriebsmitarbeiter zur Verfügung stellen möchte?
Eine potenzielle Lösung ist die Entwicklung einer Visualforce-Seite, die relevante Daten darstellt. Basierend auf dem Beitrag von Jeff Douglas habe ich ein Suchformular gebaut. Damit lassen sich alle Accounts, unabhängig von den "Freigabeeinstellungen" durchsuchen.
Im folgenden Video ist die Funktionalität dargestellt.
Vorteile:
- der Zugriff auf die neue Seite und somit auf alle Daten ist konfigurierbar
- die Darstellung der Account-Felder lässt sich über ein Feldset steuern (keine Programmierung notwendig)
- schnelle Umsetzung
- flexibel für Anpassungen
Bausteine der Visualforce-Seite:
- CSS: was fürs Auge
- JavaScript:
* Übergabe der Suchparameter an den Controller
* Identifikation der Enter-Taste mit Ausführung der Suchfunktion
- ActionStatus: visuelle Darstellung für die Ausführung der Suchfunktion
Jetzt wird's technisch ;-)
------------------------------- VUSUALFORCE SEITE -------------------------------
<apex:page
controller="f42_Search_Controller"
sidebar="false">
<style>
.linkSearchResult{
text-decoration:none
!important;
color:#015ba7
!important;
}
.loadingContainer{
width:84%;
height:88%;
border:6px
solid #CCCCCC;
border-radius:
2px;
z-index:1000;
position:absolute;
background-color:
#efefef;
filter:
alpha(opacity=90);
opacity:
0.8;
-moz-opacity:0.8;
-khtml-opacity:0.8;
}
.btn:hover
{
box-shadow:
0px 15px 20px -7px #fff inset, 0px
0px 5px -1px #333 !important;
}
.btn
{
display:
block !important;
padding:
5px 10px !important;
background:
#3ba9cd !important;
border-radius:
5px !important;
box-shadow:
0px 15px 20px -10px #fff inset, 0px
0px 5px -1px #333 !important;
border:
solid 2px #fff !important;
margin:
5px !important;
color:
#fff !important;
cursor:
pointer !important;
text-shadow:
0px 0px 2px #185375 !important;
width:95%;
}
.resultsContainer{
border-radius:
2px;
box-shadow:
0px 10px 10px 0px #efefef inset;
padding:4px;
margin-top:10px;
margin-left:2px;
}
.labelCol{
text-align:left
!important;
}
.buttonSection{
border-top:1px
solid #E0E0E0;
}
INPUT{
width:100%;
}
</style>
<script
type="text/javascript">
/************************
doSearch
*************************/
function
doSearch() {
doSearchApex(
document.getElementById("Name").value,
document.getElementById("BillingCity").value
);
}
/************************
initSearchOnEnter
*************************/
function
initSearchOnEnter(e){
if(window.event){
key = window.event.keyCode;
}else{
key
= e.which; //firefox
}
if(key
== 13) {
var
ele=document.getElementById("search-button");
ele.click();
return
false;
}else{
return
true;
}
}
</script>
<apex:form
>
<!--
--------------------------------------------------------------------
-->
<!--
APEX FUNCTIONS -->
<!--
Getting the Daschboard ID find the corresponding Monthly KAM Review
-->
<apex:actionFunction
name="doSearchApex"
action="{!runSearch}"
rerender="results,debug,errors,noSearchResults"
status="ajaxLoading">
<apex:param
name="Name"
value=""
/>
<apex:param
name="BillingCity"
value=""
/>
</apex:actionFunction>
<!--
--------------------------------------------------------------------
-->
<table
width="100%"
border="0">
<tr>
<!--
SEARCH SETTINGS -->
<!--
---------------- -->
<td
width="200"
valign="top">
<apex:pageBlock
title="{!$Label.f42_Search_Parameter}"
mode="edit"
id="criteria">
<table
cellpadding="2"
cellspacing="2"
width="100%">
<!--
ACCOUNT NAME -->
<tr>
<th
class="labelCol">
<apex:outputLabel
value="{!$ObjectType.Account.fields.Name.label}"
/><br
/>
<input
type="text"
id="Name"
onkeypress="return
initSearchOnEnter(event);" />
</th>
</tr>
<!--
CITY -->
<tr>
<th
class="labelCol">
<apex:outputLabel
value="{!$ObjectType.Account.fields.BillingCity.label}"
/><br/>
<input
type="text"
id="BillingCity"
onkeypress="return
initSearchOnEnter(event);" />
</th>
</tr>
</table>
</apex:pageBlock>
<!--
SEARCH BUTTON -->
<input
type="button"
id="search-button"
class="btn"
name="btnSearch"
value="{!$LABEL.f42_Search_Button}"
onclick="doSearch();"
/>
<!--
NUMBER OF SEARCH RESULTS -->
<apex:outputPanel
id="noSearchResults">
<div
class="resultsContainer">
<apex:outputLabel
styleclass="labelCol"
value="{!$Label.f42_Search_NumberOfResults}
{!numberSearchResults}" />
</div>
</apex:outputPanel>
</td>
<td
valign="top">
<apex:pageMessages
id="errors"
/>
<!--
********** AJAX LOADING STATUS ************-->
<apex:outputPanel
>
<apex:actionStatus
id="ajaxLoading">
<apex:facet
name="start">
<div
class="loadingContainer">
<div
style="text-align:center;
padding-top:50px;"><img
src="/img/loading32.gif"
/> </div>
</div>
</apex:facet>
</apex:actionStatus>
</apex:outputPanel>
<!--
----------------------- -->
<apex:pageBlock
mode="edit"
id="results">
<!--
LIST with ACCOUNTS -->
<!------------------------>
<apex:PageBlockTable
value="{!lstAccounts}"
var="obj">
<!--
Link View Account -->
<!--
----------------- -->
<apex:column
headerValue="">
<apex:outputLink
styleClass="linkSearchResult"
target="_top"
value="/{!obj.ID}?retURL=%2F{!obj.id}"
rendered="{!obj.OwnerId
= idCurrentUser}">{!$Label.f42_Search_View}</apex:outputLink>
</apex:column>
<!--
Get fields for displaying from FieldSet -->
<!------------------------>
<apex:repeat
value="{!$ObjectType.Account.FieldSets.SearchForm}"
var="item">
<apex:column
>
<apex:facet
name="header">
<apex:commandLink
value="{!$ObjectType.Account.fields[item].label}"
action="{!toggleSort}"
rerender="results"
status="ajaxLoading">
<apex:param
name="sortField"
value="{!item}"
assignTo="{!sortField}"/>
</apex:commandLink>
</apex:facet>
<apex:outputField
value="{!obj[item]}"
/>
</apex:column>
</apex:repeat>
</apex:PageBlockTable>
</apex:pageBlock>
</td>
</tr>
</table>
<apex:pageBlock
title="Debug
- SOQL" id="debug"
rendered="false">
<apex:outputText
value="{!debugSoql}"
/>
</apex:pageBlock>
</apex:form>
</apex:page>
------------------------------- CONTROLLER -------------------------------
public
without
sharing
class
f42_Search_Controller {
public
List<Account>
lstAccounts {get;set;} //
the collection of accounts to display
public
String idCurrentUser{get;
set;}
public
Integer numberSearchResults{get;
set;}
//
PRIVATE --------------------------------------------------
private
String API_NAME_ACCOUNT = 'Account';
private
String STANDARD_ACCOUNT_SORTFIELD = 'Name';
//-----------------------------------------------------------
private
String soqlInit;
private
String soql; // the soql without
the order and limit
private
map<String,
Schema.SObjectField> mapFieldnamesAccount;
//-----------------------------------------------------------
public
Boolean runIntoException = false; //
used by Test Class in order to run into exceptions and reach more
coverage
/************************
CONSTRUCTOR
***/
//
init the controller and display some sample data when the page loads
public
f42_Search_Controller() {
idCurrentUser
= UserInfo.getUserId();
//
get all field names and build a query
mapFieldnamesAccount
= f42_Helper.getFieldMapFromObject(API_NAME_ACCOUNT);
soqlInit
= 'SELECT '
+ f42_Helper.getFieldsAsStringFromObject(mapFieldnamesAccount) + '
' +
'FROM
' + API_NAME_ACCOUNT + '
' +
'WHERE
IsDeleted=false';
soql
= soqlInit;
}
/***********************
sortDir
***/
//
the current sort direction. defaults to asc
public
String sortDir {
get{
if(sortDir
== null)
sortDir
= 'asc';
return
sortDir;
}set;
}
/***********************
sortField
***/
//
the current field to sort by. defaults to Account Name
public
String sortField {
get{
if(sortField
== null)
sortField
= STANDARD_ACCOUNT_SORTFIELD;
return
sortField;
}set;
}
/***********************
debugSoql
***/
//
format the soql for display on the visualforce page
public
String debugSoql {
get{return
soql + ' order by '
+ sortField + ' '
+ sortDir;}
set;
}
/***********************
toggleSort
***/
//
toggles the sorting of query from asc<-->desc
public
void
toggleSort() {
//
simply toggle the direction
sortDir
= sortDir.equals('asc')
? 'desc' :
'asc';
//
run the query again
runQuery();
}
/***********************
runSearch
***/
//
build search string with parameters passed via Javascript
public
PageReference runSearch() {
soql
= soqlInit;
//
URL Parameters
Map<String,
String> mapParam = Apexpages.currentPage().getParameters();
if(mapParam
!= null){
for(String
param : mapParam.keySet()){
//
find the parameter in Account Fields Map
if(mapFieldnamesAccount.containsKey(param.toLowerCase())){
String
val = mapParam.get(param);
if(!val.equals(''))
soql
+= ' AND '
+ param + ' LIKE \''
+ String.escapeSingleQuotes(val) + '%\'';
}
}
}
//
run the query again
runQuery();
return
null;
}
/***********************
runQuery
***/
//
runs the actual query
private
void
runQuery() {
try
{
if(runIntoException)
soql
+= 'invalid soql param';
lstAccounts
= Database.query(soql + ' order by '
+ sortField + ' '
+ sortDir);
numberSearchResults
= lstAccounts.size();
if(numberSearchResults
>= f42_Helper.MAX_RECORDS_DISPLAY){
list<Account>
lstTemp = new
list<Account>();
for(Account
acc : lstAccounts){
if(lstTemp.size()
>= f42_Helper.MAX_RECORDS_DISPLAY)
break;
else
lstTemp.add(acc);
}
lstAccounts.clear();
lstAccounts.addAll(lstTemp);
ApexPages.addMessage(new
ApexPages.Message(ApexPages.Severity.Info,
LABEL.f42_Search_ToManyResult));
}
}catch
(Exception e) {
ApexPages.addMessage(new
ApexPages.Message(ApexPages.Severity.ERROR, 'ERROR:
' + e.getMessage()));
}
}
}
------------------------------- TEST CLASS -------------------------------
@isTest
private
class
f42_Test_Search_Controller {
private
static
Integer maxAccount = f42_Helper.MAX_RECORDS_DISPLAY + 1;
static
testMethod
void
testSearchForm() {
//
List with accounts (more than allowed for the search)
List<Account>
lstAccounts = new
list<Account>();
for(Integer
i=0; i<=maxAccount; i++ ){
Account
a = f42_TestDataGenerator.createTestAccount(i, false);
lstAccounts.add(a);
}
insert
lstAccounts;
test.startTest();
//*******
CHECK
system.assertnotequals(null,
lstAccounts[0].id);
//
init controller
f42_Search_Controller
cnt = new
f42_Search_Controller();
//
load the search page
Pagereference
pg = Page.f42_Search;
Test.setCurrentPage(pg);
//
test with to many accounts
cnt.runSearch();
//*******
CHECK
for(Apexpages.Message
msg : ApexPages.getMessages()){
system.assertequals(Label.f42_Search_ToManyResult,
msg.getDetail());
}
//
Number of displayed accounts is reduced to the defined max number
system.assertEquals(f42_Helper.MAX_RECORDS_DISPLAY,
cnt.lstAccounts.size());
//
search for just one account
pg.getParameters().put('Name',
lstAccounts[0].Name);
cnt.runSearch();
//*******
CHECK
system.assertEquals(1,
cnt.lstAccounts.size());
//
run other methods to reach more test coverage
cnt.toggleSort();
String
s = cnt.debugSoql;
cnt.runIntoException
= true;
cnt.runSearch();
test.stopTest();
}
}
------------------------------- CLASS f42_TestDataGenerator -------------------------------
public
with
sharing
class
f42_TestDataGenerator {
//
Account
public
static
Account
createTestAccount(Integer i, Boolean insertObject){
Account
acc = new
Account();
acc.Name
= 'TestAccount'
+ i;
if(insertObject) insert
acc;
return
acc;
}
}
------------------------------- CLASS f42_Helper -------------------------------
public
with
sharing
class
f42_Helper {
public
static
Integer MAX_RECORDS_DISPLAY = 250; //
max number of search results for the page "f42_Search"
/********************
getFieldMapFromObject
***/
public
static
Map<String, Schema.SObjectField> getFieldMapFromObject(String
objName){
return
Schema.getGlobalDescribe().get(objName).getDescribe().fields.getMap();
}
/********************
getFieldListFromObject
***/
public
static
String getFieldsAsStringFromObject(Map<String,
Schema.SObjectField> mapFields){
String
strFields='';
for
( String f : mapFields.keySet() ){
strFields
+= f;
strFields
+= ', ';
}
strFields
= strFields.subString(0,strFields.Length() -2);
return
strFields;
}
}
Kommentare
Kommentar veröffentlichen