我构建了一个Visualforce页面,它将Contacts和Lead都拉到一个视图中。它的工作效果很好,只是加载需要20-25秒,众所周知,这对于可用性来说是可怕的。
我已经查看了日志,看起来Apex占用了70%的加载时间,30%用于javascript部分,如果需要,我可以发布日志。
注:本项目基于Mohit Shrivastav的工作。在项目进行到一半的时候,我决定使用datatables.net而不是Angular来进行排序,因为我还在学习Angular。
这是控制器:
public with sharing class recentMqlExplorerController {
public String LeadContactList {
get; set;
}
public class ContactLeadWrap {
public String id;
public String name;
public String dateofmql;
public String company;
public Decimal leadscore;
public String country;
public String state;
public Integer employees;
public String ownername;
public String leadstatus;
public String leadorcontact;
public String team;
public String url;
ContactLeadWrap() {
id = '';
name = '';
company = '';
leadscore = 0;
country = '';
state = '';
employees = 0;
ownername = '';
leadstatus = '';
url = '';
team = '';
}
}
//Method to bring the list of Contacts and Leads and Serialize Wrapper Object as JSON
public static String getlstContactLead() {
List <ContactLeadWrap> lstwrap = new List <ContactLeadWrap> ();
List <Contact> lstcontact = new List<Contact>();
List <Lead> lstlead = new List<Lead>();
Datetime now = System.now();
Datetime last = now.addDays(-60);
Id ownerId = UserInfo.getUserId();
lstcontact = [SELECT Id, FirstName, Name, Date_Became_MQL__c, Account.Name, mkto2__Lead_Score__c, Account.BillingCountry, Account.BillingState, Account.NumberOfEmployees, Owner.Name, z_Lead_Status_at_Convert_TEXT_if_applcbl__c, Account.AOU__r.sub_sub_Team_Picklist__c FROM Contact WHERE (Date_Became_MQL__c > :last AND Owner.Id = :ownerId) OR FirstName = 'westest' LIMIT 20000];
lstlead = [SELECT Id, FirstName, Name, Date_Became_MQL__c, Company, mkto2__Lead_Score__c, Country, State, NumberOfEmployees, Owner.Name, status, z_Lead_Owner_User__r.sub_sub_Team_Picklist__c FROM Lead WHERE (Date_Became_MQL__c > :last AND IsConverted = false AND Owner.Id = :ownerId) OR (FirstName = 'westest' AND IsConverted = false) LIMIT 20000];
String convertedDateofmql = null;
Date dateOnly = null;
String fullRecordURL = null;
for (Contact c: lstcontact) {
if (c.Date_Became_MQL__c != null) {
convertedDateofmql = c.Date_Became_MQL__c.format('MM/dd/YYYY');
}
if (c.z_Lead_Status_at_Convert_TEXT_if_applcbl__c == null) {
c.z_Lead_Status_at_Convert_TEXT_if_applcbl__c = 'no status';
}
fullRecordURL = URL.getSalesforceBaseUrl().toExternalForm() + '/' + c.Id;
ContactLeadWrap cwrap = new ContactLeadWrap();
cwrap.id = c.id;
cwrap.name = c.name;
cwrap.url = fullRecordURL;
cwrap.dateofmql = convertedDateofmql;
cwrap.company = c.Account.Name;
cwrap.leadscore = c.mkto2__Lead_Score__c;
cwrap.country = c.Account.BillingCountry;
cwrap.state = c.Account.BillingState;
cwrap.employees = c.Account.NumberOfEmployees;
cwrap.ownername = c.Owner.Name;
cwrap.leadstatus = c.z_Lead_Status_at_Convert_TEXT_if_applcbl__c;
cwrap.leadorcontact = 'Contact';
cwrap.team = c.Account.AOU__r.sub_sub_Team_Picklist__c;
lstwrap.add(cwrap);
}
for (Lead l: lstlead) {
if (l.Date_Became_MQL__c != null) {
convertedDateofmql = l.Date_Became_MQL__c.format('MM/dd/YYYY');
}
fullRecordURL = URL.getSalesforceBaseUrl().toExternalForm() + '/' + l.Id;
ContactLeadWrap lwrap = new ContactLeadWrap();
lwrap.id = l.id;
lwrap.name = l.name;
lwrap.url = fullRecordURL;
lwrap.dateofmql = convertedDateofmql;
lwrap.company = l.Company;
lwrap.leadscore = l.mkto2__Lead_Score__c;
lwrap.country = l.Country;
lwrap.state = l.State;
lwrap.employees = l.NumberOfEmployees;
lwrap.ownername = l.Owner.Name;
lwrap.leadstatus = l.status;
lwrap.leadorcontact = 'Lead';
lwrap.team = l.z_Lead_Owner_User__r.sub_sub_Team_Picklist__c;
lstwrap.add(lwrap);
}
return JSON.serialize(lstwrap);
}
public class getInformation {
}
}
这是页面:
<apex:page standardStylesheets="false" sidebar="false" showHeader="false" docType="html-5.0" controller="recentMqlExplorerController">
<html xmlns:ng="http://angularjs.org" ng-app="hello" lang="en">
<head>
<meta charset="utf-8"></meta>
<meta http-equiv="X-UA-Compatible" content="IE=edge"></meta>
<title>MQL Explorer</title>
<meta name="description" content=""></meta>
<meta name="viewport" content="width=device-width"></meta>
<link rel="stylesheet" href="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/sass-bootstrap/dist/css/bootstrap.css')}"/>
<link rel="stylesheet" href="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/styles/main.css')}"/>
<link type='text/css' href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400,300' rel='stylesheet'/>
</head>
<body>
<!-- =========== Binding Controller to Body of Page ============= -->
<div class="jumbotron ribbon">
<h2>Hello {!$User.FirstName}:</h2> <h4> Welcome to the Recent MQL Dashboard</h4>
</div>
<div class="jumbotron banner row">
<span class="chevron"><i class="fa fa-chevron-down fa-1x"></i></span>
<div class="jumbotron jbot"></div>
<div class="dashboard">
</div>
<div class="dashboard-shadow"></div>
</div>
<div ng-controller="ctrlRead" class="container table-container">
<div class="table-container-header">
<h4>Qualified Leads and Contacts</h4>
</div>
<table id="dashboard" class="table-borders display responsive" width="100%">
<thead>
<tr role="row" class="tableFilters">
<th>Search by Name</th>
<th>From when?</th>
<th>Search by Company</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th id="owner">Search by Owner</th>
<th>What Status?</th>
<!-- <th>What Type?</th> -->
<th>What Team?</th>
</tr>
<tr role="row" class="tableHeader">
<th scope="col" class="name">Name</th>
<th scope="col" class="dateofmql" width="250px">Date of MQL</th>
<th scope="col" class="company">Company</th>
<th scope="col" class="leadscore">Score</th>
<th scope="col" class="country">Country</th>
<th scope="col" class="state">State</th>
<th scope="col" class="employees">EE#</th>
<th scope="col" class="ownername">Owner Name</th>
<th scope="col" class="leadstatus">Lead Status</th>
<!-- <th scope="col" class="leadorcontact">Type</th> -->
<th scope="col" class="team">Sales Team</th>
</tr>
</thead>
<tbody class="table">
<tr ng-repeat="item in pagedItems[currentPage] | orderBy:sortingOrder:reverse">
<th><apex:outputlink style="text-decoration: underline; color: #01B2E4;" target="_blank" value="{{item.url}}">{{item.name}}</apex:outputlink></th>
<td>{{item.dateofmql}}</td>
<td>{{item.company}}</td>
<td>{{item.leadscore}}</td>
<td>{{item.country}}</td>
<td>{{item.state}}</td>
<td>{{item.employees}}</td>
<td>{{item.ownername}}</td>
<td>{{item.leadstatus}}</td>
<td>{{item.team}}</td>
</tr>
</tbody>
</table>
</div>
<script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/angular/angular.min.js')}"/>
<script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/jquery.min.js')}"/>
<script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/jquery-ui.min.js')}"/>
<script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/jquery.dataTables.min.js')}"/>
<script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/dataTables.tableTools.min.js')}"/>
<script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/dataTables.responsive.min.js')}"/>
<script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/jquery.dataTables.columnFilter-min.js')}"/>
<script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/angular-force/ngForce.min.js')}"/>
<script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/scripts/app-min.js')}"/>
<script type="text/javascript">
'use strict';
<!-- Name your application -->
var myapp = angular.module('hello', []);
var sortingOrder = 'name';
<!-- Define Controller -->
var contrl=myapp.controller('ctrlRead', function ($scope, $filter) {
<!--- Initialize Scope Variables --->
$scope.sortingOrder = sortingOrder;
$scope.reverse = false;
$scope.filteredItems = [];
$scope.groupedItems = [];
$scope.itemsPerPage = 60000;
$scope.pagedItems = [];
$scope.currentPage = 0;
$scope.items ={!lstContactLead};
var searchMatch = function (haystack, needle) {
if (!needle) {
return true;
}
return haystack.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
};
//Initialize the Search Filters
$scope.search = function () {
$scope.filteredItems = $filter('filter')($scope.items, function (item) {
for (var attr in item) {
if (searchMatch(item[attr], $scope.query))
return true;
}
return false;
});
// Define Sorting Order
if ($scope.sortingOrder !== '') {
$scope.filteredItems = $filter('orderBy')($scope.filteredItems, $scope.sortingOrder, $scope.reverse);
}
$scope.currentPage = 0;
// Group by pages
$scope.groupToPages();
};
// Calculate Total Number of Pages based on Records Queried
$scope.groupToPages = function () {
$scope.pagedItems = [];
for (var i = 0; i < $scope.filteredItems.length; i++) {
if (i % $scope.itemsPerPage === 0) {
$scope.pagedItems[Math.floor(i / $scope.itemsPerPage)] = [$scope.filteredItems[i]];
} else {
$scope.pagedItems[Math.floor(i / $scope.itemsPerPage)].push($scope.filteredItems[i]);
}
}
};
$scope.range = function (start, end) {
var ret = [];
if (!end) {
end = start;
start = 0;
}
for (var i = start; i < end; i++) {
ret.push(i);
}
return ret;
};
$scope.prevPage = function () {
if ($scope.currentPage > 0) {
$scope.currentPage--;
}
};
$scope.nextPage = function () {
if ($scope.currentPage < $scope.pagedItems.length - 1) {
$scope.currentPage++;
}
};
$scope.setPage = function () {
$scope.currentPage = this.n;
};
// functions have been describe process the data for display
$scope.search();
// change sorting order
$scope.sort_by = function (newSortingOrder) {
if ($scope.sortingOrder == newSortingOrder)
$scope.reverse = !$scope.reverse;
$scope.sortingOrder = newSortingOrder;
// icon setup
$('th i').each(function () {
// icon reset
$(this).removeClass().addClass('icon-sort');
});
if ($scope.reverse)
$('th.' + new_sorting_order + ' i').removeClass().addClass('icon-chevron-up');
else
$('th.' + new_sorting_order + ' i').removeClass().addClass('icon-chevron-down');
};
});
contrl.$inject = ['$scope', '$filter'];
</script>
</body>
</html>
</apex:page>
请查看salesforce提供的以下指导原则,这可能有助于提高页面性能。
较大的页面大小直接影响加载时间。改善Visualforce页面加载时间:
缓存任何经常访问的数据,如图标图形。
在Apex控制器getter方法中避免SOQL查询。
通过以下方式减少页面上显示的记录数:
限制从Apex控制器中的SOQL调用返回的数据。例如,在WHERE子句中使用AND语句,或删除空结果
利用列表控制器的分页功能,每页显示更少的记录
"延迟加载"Apex对象以减少请求时间。
考虑将任何JavaScript移到标记之外,并将其放在结束标记之前的标记中。标记将JavaScript放在结束元素之前;因此,Visualforce试图在页面上的任何其他内容之前加载JavaScript。但是,只有当您确信JavaScript不会对您的页面产生任何不利影响时,才应该将其移动到页面底部。例如,需要document.write或事件处理程序的JavaScript代码片段应该保留在元素中。
在任何情况下,Visualforce页面都必须小于15 MB。
问候,
Naveen
http://www.autorabit.com
只需粗略地查看一下您的代码,以下是我的观察结果。
1) 您使用了太多存储在脚本中的脚本,如angular、jquery、Bootstrap等。使用这些脚本会造成时间滞后。不确定您的需求,但要寻找限制外部脚本使用的方法。
2) 不使用Salesforce标签-几乎完整的页面是基于JAVA的,插入到顶点标签中。例如,使用apex表格,而不是使用表格tr和td标签。
使用标准的开箱即用功能将尝试减少页面的加载时间,并尝试利用本机Salesforce脚本。
3) 如果你不使用很多标签,我甚至建议使用自定义脚本而不是引导程序。在Salesforce工作时,尽量保持Salesforce的外观和感觉,这将产生最佳效果。
免责声明-这些是优化页面加载的一般建议,尽管特定要求可能会强制使用本机脚本。
Ashish