Salesforce pagination custom controller example

Salesforce pagination custom controller example

Всем привет. Нужна помощь опытных программистов.

Задача (тестовое задание).

1. Необходимо создать VF страницу и отобразить следующие поля: Name (link), Email, Contact Level (picklist), Account (lookup), Owner (lookup), Created By (lookup), Created Date.
2. Таблица должна поддерживать пагинацию (по 10 контактов на странице) и сортировку по любому столбцу
(по клику на заголовке).
3. Напротив каждого контакта нужно показать кнопку Del, которая будет удалять контакт из базы данных.
4. Также на этой странице должно быть поле для поиска контактов по имени (частичное совпадение по
first/last name).
5. И кнопка Search. Результаты поиска должны отображаться в той же таблице и должны показывать те
же столбцы.
6. Кроме этого необходимо сделать кнопку ‘New Contact’, которая откроет новую страницу или покажет popup
на той же странице, где можно будет ввести First Name, Last Name *, Email *, Contact Level, Account и нажать Save или Cancel

Код на текущем этапе:
https://gitlab.com/domovikx/tx002/tree/master
пост обновлен (2018.11.25) код на гите

На текущем этапе, ПОЖАЛУЙСТА помогите разобраться, как правильно сделать пагинацию в кастомном контроллере?

У кого такое же задание или кто может помочь и сидит в ВК, пожалуйста пишите в личку vk.com/domovikx любой помощи пуду рад, спасибо заранее!

Вот тут есть пример
https://salesforce-developer.ru/paginatsiya-na-visualforce-stranitse-pagination-using-standardsetcontroller

gitlab.com/domovikx/tx002
Да, классно уже народ подсказал, тут мои текущие коды, когда закончу ТЗ, выложу, что получилось на форуме в чистовом варианте.

Промежуточный этап. Пагинация + сортировка готовы и работают.


<apex:page controller="CustomSorting181112Controller">

<!--
Вариант с сортировкой по столбцу
'SELECT Id, Name, Email, Contact_Level__c, AccountId, OwnerId, CreatedById, CreatedDate '

Необходимо отобразить следующие поля:
1 Name (link), 2 Email, 3 Contact Level (picklist), 4 Account (lookup),
5 Owner (lookup), 6 Created By (lookup), 7 Created Date.
-->

<apex:form>
<apex:pageBlock title="Тестовое задание Success Craft 181108.009" id="thisBlock">

<!-- табличка -->
<apex:pageBlockTable value="{! Contact }" var="ct" id="thisTable">

<apex:column value="{! ct.Name }">
<apex:facet name="header">
<apex:commandLink action="{! sortByName }" reRender="thisBlock">
<apex:outputText value="1. {! $ObjectType.Contact.Fields.Name.Label }" />
</apex:commandLink>
</apex:facet>
</apex:column>

<apex:column value="{! ct.Email }">
<apex:facet name="header">
<apex:commandLink action="{! sortByEmail }" reRender="thisBlock">
<apex:outputText value="2. {! $ObjectType.Contact.Fields.Email.Label }" />
</apex:commandLink>
</apex:facet>
</apex:column>

<apex:column value="{! ct.Contact_Level__c }">
<apex:facet name="header">
<apex:commandLink action="{! sortByContactLevel }" reRender="thisBlock">
<apex:outputText value="3. {! $ObjectType.Contact.Fields.Contact_Level__c.Label }" />
</apex:commandLink>
</apex:facet>
</apex:column>

<apex:column value="{! ct.AccountId }">
<apex:facet name="header">
<apex:commandLink action="{! sortByAccountId }" reRender="thisBlock">
<apex:outputText value="4. Accounts" />
</apex:commandLink>
</apex:facet>
</apex:column>

<apex:column value="{! ct.OwnerId }">
<apex:facet name="header">
<apex:commandLink action="{! sortByOwnerId }" reRender="thisBlock">
<apex:outputText value="5. Owners" />
</apex:commandLink>
</apex:facet>
</apex:column>

<apex:column value="{! ct.CreatedById }">
<apex:facet name="header">
<apex:commandLink action="{! sortByCreatedById }" reRender="thisBlock">
<apex:outputText value="6. Created By" />
</apex:commandLink>
</apex:facet>
</apex:column>

<apex:column value="{! ct.CreatedDate }">
<apex:facet name="header">
<apex:commandLink action="{! sortByCreatedDate }" reRender="thisBlock">
<apex:outputText value="7. {! $ObjectType.Contact.Fields.CreatedDate.Label }" />
</apex:commandLink>
</apex:facet>
</apex:column>

</apex:pageBlockTable>

<!-- кнопочки вперед-назад... -->
<div align="center" id="button">
<apex:commandButton action="{! setList.first }" value=" << " title="First Page" disabled="{!!setList.HasPrevious}" reRender="thisBlock,button"
/>
<apex:commandButton action="{! setList.previous }" value=" Previous " disabled="{!!setList.HasPrevious}" reRender="thisBlock,button"
/>
<apex:commandButton action="{! setList.next }" value=" Next > " disabled="{!!setList.HasNext}" reRender="thisBlock,button"
/>
<apex:commandButton action="{! setList.last }" value=" >> " title="Last Page" disabled="{!!setList.HasNext}" reRender="thisBlock,button"
/>

<!-- поле - колво страниц -->
<span style="float:right">
<apex:outputLabel value=" Page " />
<apex:InputText value="{! PageNumber }" maxLength="4" size="1" />
<apex:outputLabel value=" of {! TotalPages }" />
</span>

</div>

<div>
<!-- выпадающий список - типо пагинация -->
<span style="float:right">
<apex:SelectList value="{! PageSize }" size="1">
<apex:selectOptions value="{! PageSizeList }" />
<apex:actionSupport event="onchange" reRender="thisBlock" />
</apex:SelectList>
</span>
</div>

<!-- ссылка - создать новый аккаунт -->
<apex:outputLink value="{!URLFOR($Action.Contact.NewContact)}" target="_blank">
Create New Contact
</apex:outputLink>

</apex:pageBlock>

</apex:form>
</apex:page>

public class CustomSorting181112Controller {

public String sortingColumn = 'Name'; // первичный столбец
public String sortingOrder = ' ASC '; // первичная сортировка
public String column = sortingColumn; // первичное значение нового столбца

public String query = 'SELECT Id, Name, Email, Contact_Level__c, AccountId, OwnerId, CreatedById, CreatedDate ' +
'FROM Contact ' +
'ORDER BY ' + sortingColumn + sortingOrder;

// Сортировка начинается здесь ---------------------------------------
// вначале пишем методы которые будут вызываться командой со стринички
public void sortByName() {
sortingColumn = 'Name';
sortingOrder();
}
public void sortByEmail() {
sortingColumn = 'Email';
sortingOrder();
}
public void sortByContactLevel() {
sortingColumn = 'Contact_Level__c';
sortingOrder();
}
public void sortByAccountId() {
sortingColumn = 'AccountId';
sortingOrder();
}
public void sortByOwnerId() {
sortingColumn = 'OwnerId';
sortingOrder();
}
public void sortByCreatedById() {
sortingColumn = 'CreatedById';
sortingOrder();
}
public void sortByCreatedDate() {
sortingColumn = 'CreatedDate';
sortingOrder();
}

// проверяем- новый столбец или тот же
// если столбец новый сортировка - ASC и обновляется запрос.
public void sortingOrder() {
if (sortingColumn == column) {
sortingOrder = (sortingOrder == ' ASC ') ? ' DESC ' : ' ASC ';
} else {
sortingOrder = ' ASC ';
column = sortingColumn;
}
// получаем наш запрос с нужной нам сортировкой, аминь!
query = 'SELECT Id, Name, Email, Contact_Level__c, AccountId, OwnerId, CreatedById, CreatedDate ' +
'FROM Contact ' +
'ORDER BY ' + sortingColumn + sortingOrder;

// после сортировки ОБЯЗАТЕЛЬНО создается новый экземпляр контроллера с новыми значениями
setList = new ApexPages.StandardSetController(Database.query(query));
}
// конец сортировки --------------------------------------

// Пагинация начинается здесь ---------------------------------
// Вначале делаем выпадающий список - количество записей на странице
public list<SelectOption> getPageSizeList(){ // выпадающий список на странице
list<SelectOption> options = new list<SelectOption>();
options.add(new selectOption('5','5'));
options.add(new selectOption('10','10'));
options.add(new selectOption('25','25'));
options.add(new selectOption('50','50'));
options.add(new selectOption('100','100'));
return options;
}
public ApexPages.StandardSetController setList {
get {
if (setList == null) { // проверка на наличие экземпляра контроллера
setList = new ApexPages.StandardSetController(Database.query(query));
}
if (this.PageSize == null) PageSize = 10; // дефаултное значение записей/стр, попробуй 7 или 8 =)
setList.setPageSize(PageSize); // количество записей/страница
return setList;
} set;
}
public Integer PageSize {
get; set { // Выбираем количество записей на странице
if(value != null) this.PageSize = value;
}
}
public Integer PageNumber {
get { // получаем текущий номер страницы
this.PageNumber = setList.getPageNumber();
return this.PageNumber;
}
set { // это чтобы перейти к введенному номеру страницы
setList.setPageNumber(value); //
}
}
public Integer TotalPages { // Считаем количество страниц
// это я скопировал у других, кто знает как это упростить пишите. это работает
get {
if (setList.getResultSize() <= 10)
this.TotalPages = 1;
if (Math.Mod ( setList.getResultSize(),setList.getPageSize() ) == 0)
this.TotalPages = ( setList.getResultSize()/setList.getPageSize() );
else this.TotalPages = ( setList.getResultSize()/setList.getPageSize() )+1;
return totalpages;
}
set;
}
public List<Contact> getContact() {
return (List<Contact>) setList.getRecords();
}
// Конец пагинации ----------------------------------------

} // конец контроллера =)


Текущий этап.
Как добавить столбец с кнопками - Edit Del
Как добавить поиск.
Возможно поможет этот код: https://developer.salesforce.com/forums/?id=9060G000000UVLGQA4

На вопрос "КАК СДЕЛАТЬ" сложно дать ответ. Существует 100500 способов сделать одну и ту же штуку.
Это основы! Советую сначала погуглить тему создания CRUD приложение для Salesforce.
Если уже не будет получаться, можно детальнее пообщаться на тему "ПОЧЕМУ".

К тому же тут на форуме присутствуют представители различных компаний и в том числе той куда ты планируешь попасть сделав тестовое задание. Возможность находить решения в интернете и адаптировать их под свои требования ценится не меньше чем наличие знаний

На форуме уже не раз обсуждались эти вопросы.
Можно "delete" через поиск поискать.
Вот к примеру
https://salesforce-developer.ru/forum/topic-delete-function
Где упоминается
http://salesforcesource.blogspot.com/2009/09/edit-and-delete-command-for-your.html
С полностью готовым решением.

На счет поиска тоже упоминалось не раз.
Вот к примеру поиск выдал
https://salesforce-developer.ru/forum/topic-nuzhna-pomosch-1ac98fe6-a206-493a-ada3-a5b48d6465da

https://salesforce.stackexchange.com/questions/115757/soql-like-and-wildcards-not-returning-full-results
Как писать динамический запрос для поиска.
Примеры тут:

String query = 'SELECT Id, Name FROM Account WHERE name LIKE \'%' + searchVar + '%\'';

List<Account> accts = Database.query(query);

List<Account> accts = [SELECT Id, Name FROM Account WHERE name LIKE :('%' + searchName + '%')];

Второй вариант предпочтительнее из-за правильного биндинга переменных в запрос.
В первом случае можно словить SOQL Injection если правильно не проверить переменную searchVar.
Это советы на будущее.

Спасибо за примеры. Помогло. Сегодня сделал колонку с edit / del
особенно помог пример:
http://salesforcesource.blogspot.com/2009/09/edit-and-delete-command-for-your.html

текущие рабочие коды можно посмотреть тут: https://gitlab.com/domovikx/tx002/tree/master

Следующая задача
- Сделать кнопку ‘New Contact’,
- которая откроет popup на той же странице,
- где можно будет ввести: First Name, Last Name *, Email *, Contact Level, Account
- и нажать Save или Cancel

Кто можешь подсказать ресурсы, которые почитать? Благодарю заранее!

DomovikX
- которая откроет popup на той же странице,

Если хочется именно popup то это уже надо смотреть в сторону Javascript. Честно даже не представляю как это сделать с помощью чистого VF.

Итак это popup in salesforce решается с помощью модальных окон.
Create a Modal Popup in Salesforce

для контроллера

// Наш попап

public boolean displayPopup {get; set;}
public void popupShow(){
displayPopup = true;
}
public void popupClose(){
displayPopup = false;
}
// конец попапа

для страницы

<!-- Popup -->

<apex:commandButton value="Create New Contact Popup" action="{!popupShow}" rerender="popupPanel" />
<apex:outputPanel id="popupPanel">
<apex:outputPanel styleClass="popupBackground" layout="block" rendered="{!displayPopUp}" />
<apex:outputPanel styleClass="popupCust" layout="block" rendered="{!displayPopUp}">

<h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">Лучший в мире заголовок</h2>
<p> Clicking on this link you can express your financial gratitude to the developer of this application in dollars equivalent.
<a href="https://vk.com/domovikx" target="_blank">Your developer DomovikX</a>.</p>

<apex:commandButton value="Close" action="{!popupClose}" rerender="popupPanel" />
</apex:outputPanel>
</apex:outputPanel>

<style type="text/css">
.popupCust {
background-color: white;
border-width: 0px;
border-style: solid;
z-index: 9001;
left: 50%;
padding: 11px;
position: absolute;
width: 600px;
margin-left: -240px;
top: 100px;
}

.popupBackground {
background-color: black;
opacity: 0.20;
filter: alpha(opacity=20);
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 9000;
}
</style>

От млин уже за столько лет и позабыл про "rerender". Только хотел поругаться что для открытия окна перезагружать страницу сильно нерационально. А оказывается вот оно как Перезагрузки нет, но все равно задержка присутствует. Не самый оптимальный вариант, но полностью классический для Visualforce.

В принципе раз на бэкенде нет никакой хитрой логики на открытие окна то лучше заморочиться css + jquery.

Пользуясь случаем.
Как написать кнопку для открытия ссылки в новой странице. Имитация ссылки под кнопку.

<!-- Это имитация ссылки под кнопку Create New Contact -->

<apex:commandLink target="_blank" styleClass="btn" style="text-decoration:none; padding:4px; float:right" action="{!URLFOR($Action.Contact.NewContact)}"
value="Create New Contact linke" />
<!-- аналог в виде ссылки
<apex:outputLink value="{!URLFOR($Action.Contact.NewContact)}" target="_blank" style="font-weight:bold; float:right">
Create New Contact
</apex:outputLink>
-->

Люди добрый, подскажите, как сделать:
поля First Name, Last Name (обязательное), Email (обязательное с проверкой на емайл), Contact Level (список), Account
и кнопки Save или Cancel

Сейчас разбираю это. Спасибо за ссылки на инфу.

https://salesforce.stackexchange.com/questions/180172/using-custom-controller-to-create-new-contact
https://success.salesforce.com/answers?id=9063A000000e1U2QAI

Сейчас хочу попробовать вот этот вариант - Using custom controller to create new Contact

Вот кривой но рабочий код. работает ровно на одну запись. Как доделаю, перезалью пост.

<apex:page controller="ContactCreateController">

<!--
- 1.First Name, 2.Last Name *, 3.Email *, 4.Contact Level, 5.Account
- кнопки - Save или Cancel
- * обязательные
- Contact Level - список: Primary, Secondary, Tertiary
-->
<apex:pageBlock>
<apex:form>
<apex:pageBlockSection columns="1">
<apex:inputText label="1. First Name" value="{!firstName}" />
<apex:inputText label="2. Last Name" value="{!lastName}" />
<apex:inputText label="Phone" value="{!phone}" />
<apex:commandButton value="Save" action="{!save}" />
</apex:pageBlockSection>
</apex:form>
</apex:pageBlock>
</apex:page>

public class ContactCreateController {

public String firstName {get; set;}
public String lastName {get; set;}
public String phone {get; set;}
list <contact> conList;

public ContactCreateController(){
conlist=new List<contact>();
}

public pageReference save(){
Contact con = new Contact(firstname = firstname, lastname = lastname, phone = phone);
conList.add(con);
insert conList;
return null;
}

}

Немнго оптимизации.
DML операции работают не только с List, но и с одиночными записями.
в строке 15 можно написать

insert con;

и тогда не нужны будут 6, 9, 14

Как и обещал. Код для добавления нового контакта+аккаунт.
Добавляется в окно попап.
Код полностью можно посмотреть на гите:
https://gitlab.com/domovikx/tx002/tree/master

Код для страницы

<!-- Popup -->

<apex:commandButton value=" Create a New Contact " action="{!popupShow}" rerender="popupPanel"/>
<apex:outputPanel id="popupPanel">
<apex:outputPanel styleClass="popupBackground" layout="block" rendered="{!displayPopUp}" />
<apex:outputPanel styleClass="popupCust" layout="block" rendered="{!displayPopUp}">

<h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">Create a New Contact</h2>
<p align="center"> Clicking on this link you can express your financial gratitude to the developer of this application
in dollars equivalent. Your developer
<a href="https://vk.com/domovikx" target="_blank" style="font-weight:bold"> VK.com/DomovikX </a>.</p>

<!-- Create New Contact-->
<!--
- 1.First Name, 2.Last Name *, 3.Email *, 4.Account
- 5.Contact Level,
- кнопки - Save или Cancel
- * обязательные
- Contact Level - список: Primary, Secondary, Tertiary
-->
<p>
<apex:pageBlockSection columns="2">
<apex:inputText label="1. First Name" value="{!newFirstName}" />
<apex:inputText label="2. Last Name * " value="{!newLastName}" required="true" />
<apex:inputText label="3. E-mail * " value="{!newEmail}" required="true" />
<apex:inputText label="4. Phone" value="{!newPhone}" />
<apex:inputText label="5. Account" value="{!newAccount}" />

<apex:SelectList label="6. Contact Level" size="1" value="{!newContactLevel}">
<apex:selectOptions value="{!ContactLevel}" />
</apex:SelectList>

</apex:pageBlockSection>
<p align="right"><span class="colorTextBold">*</span> - Required fields</p>
<p align="center">
<apex:commandButton value="Save" action="{!save}" rerender="popupPanel" />
<apex:commandButton value="Close" immediate="true" html-formnovalidate="formnovalidate" action="{!popupClose}" rerender="popupPanel"/>
</p>
</p>
<apex:pageMessages />
<!-- сообщение об ощибках сюда -->
</apex:outputPanel>
</apex:outputPanel>

код для контроллера

// Добавление нового контакта --------------------------------

public String newFirstName {get; set;} // 1
public String newLastName {get; set;} // 2
public String newEmail {get; set;} // 3
public String newPhone {get; set;} // 4
public String newAccount {get; set;} // 5

public String newContactLevel {get; set;} // 6 . Сетим выбранное в списке
public list<SelectOption> getContactLevel(){ // Гетим наш список
list<SelectOption> ContactLevel = new list<SelectOption>();
ContactLevel.add(new SelectOption('Primary','Primary'));
ContactLevel.add(new SelectOption('Secondary','Secondary'));
ContactLevel.add(new SelectOption('Tertiary','Tertiary'));
return ContactLevel;
}

// памятка по API name
// https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_contact.htm
public pageReference save(){

if (newAccount != '') { // если строчка Аккаунта имеет данные

try { // пробуем добавить данные в БД
Account acc = new Account(Name = newAccount);
insert acc; // Вначале добавляем аккаунт, чтобы потом взять его ID

Contact con = new Contact(
FirstName = newFirstName, LastName = newLastName, Email = newEmail,
Phone = newPhone, Contact_Level__c = newContactLevel, AccountId = acc.ID);
insert con;
}
catch (DMLException e) {
ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR,'Error creating new contact.'));
return null;
}
}
else { // если строчка Аккаунта пуста

try {
Contact con = new Contact(
FirstName = newFirstName, LastName = newLastName, Email = newEmail,
Phone = newPhone, Contact_Level__c = newContactLevel);
insert con;
}
catch (DMLException e) {
ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR,'Error creating new contact.'));
return null;
}
}

ApexPages.addMessage(new ApexPages.message(ApexPages.severity.CONFIRM,'New contact "'+newFirstName+' '+newLastName+'" successfully created.'));
newFirstName=''; // возвращаем значения поумолчанию
newLastName='';
newEmail='';
newPhone='';
newAccount='';
newContactLevel='Primary';

//popupClose(); // если понадобится закрытие окошка сразу после создания
return null;
}
// -----------------------------------------------------------

Ура. Спасибо форуму, и в особенности Димке за источники.
Мое ТЗ готово. Остались Юнит тесты.
В результате получилось следующее.

Поздравляю с небольшой победой. Смотрится красиво!
Маленький совет для улучшения UI. Панель Errors обычно выводят НАД формой.
Помести ее между приветствием и формой и будет вообще супер!

Илья, при таком

<apex:inputText label="2. Last Name * " value="{!newLastName}" required="true" />

подходе у меня при незаполненном поле, проверка выдает
j_id0:form:j_id6: Validation Error: Value is required
Как сделать нормальным?

SMLG
Илья, при таком
<apex:inputText label="2. Last Name * " value="{!newLastName}" required="true" />

подходе у меня при незаполненном поле, проверка выдает
j_id0:form:j_id6: Validation Error: Value is required
Как сделать нормальным?

Так и правильно выдает.
Last Name - Это поле поумолчанию стоит как обязательное, даже если required="false", оно надо.
А вот e-mail - делается обязательным полем.

Остальной код с правками на гите: https://gitlab.com/domovikx/tx002/tree/master.
Пока открыт, потом возможно попросят закрыть. Если недоступен, значит закрыл.

DomovikX
public ApexPages.StandardSetController setList {
get {
if (setList == null) { // проверка на наличие экземпляра контроллера
setList = new ApexPages.StandardSetController(Database.query(query));
}
if (this.PageSize == null) PageSize = 10; // дефаултное значение записей/стр, попробуй 7 или 8 =)
setList.setPageSize(PageSize); // количество записей/страница
return setList;
} set;
}

public ApexPages.StandardSetController setList {

get {
if (setList == null) { // проверка на наличие экземпляра контроллера
setList = new ApexPages.StandardSetController(Database.query(query));
}
if (this.PageSize == null) PageSize = 10; // дефаултное значение записей/стр, попробуй 7 или 8 =)
setList.setPageSize(PageSize); // количество записей/страница
return setList;
} set;
}

Как в тесте "запустить" setList, чтобы тест зашел в него?

Интересная информация? Помогите сайту, разместите ссылку в социальных сетях..